diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 00000000..1c5b7a3d --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,23 @@ +[bumpversion] +current_version = 0.8.0-dev +commit = True +tag = True +parse = (?P\d+)\.(?P\d+)\.(?P\d+)-?(?Pdev)? +serialize = + {major}.{minor}.{patch}-{release} + {major}.{minor}.{patch} + +[bumpversion:file:pyproject.toml] + +[bumpversion:file:src/ics/__init__.py] + +[bumpversion:file:doc/event.rst] + +[bumpversion:file:README.rst] + +[bumpversion:part:release] +values = + dev + release +optional_value = release + diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index d339f15a..00000000 --- a/.coveragerc +++ /dev/null @@ -1,2 +0,0 @@ -[run] -omit = ics/tools.py, ics/__init__.py, ics/__meta__.py, ics/alarm/__init__.py, ics/types.py diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 8785d217..21e84e57 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -17,13 +17,13 @@ jobs: uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip setuptools - pip install -r requirements.txt -r dev/requirements-test.txt - - name: Static checking with mypy - run: | - mypy ics - - name: Run pytest - run: | - python setup.py test + - name: Report timezone information + run: python -c "import time; print((time.timezone, time.altzone, time.daylight, time.tzname, time.time()))" + - name: Install tox + run: python -m pip install --upgrade tox tox-gh-actions + - name: Run tox + run: tox + - name: Publish coverage + run: + rm -rf ./.mypy_cache/; + bash <(curl https://codecov.io/bash) diff --git a/.gitignore b/.gitignore index 3f2a5324..b27fc4d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,45 +1,142 @@ -# Compiled python modules. -*.pyc +/.idea +/venv +/.venv -# Setuptools distribution folder. -dist/ -build/ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class -# Python egg metadata, regenerated from source files by setuptools. -/*.egg-info -/*.egg +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ .eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt -# Virtualenvs -ve -ve3 - -bin -include -lib -local -venv -venv3 -/bin -/include/ -/lib/ - -# doc builds -doc/_build/* - -#tests -.cache/ +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ .coverage -htmlcov -.pytest_cache -/.pytest_cache/ +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ -# IDE settings -/.idea/ +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy .mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ -# pffff -.idea -pip-selfcheck.json -/pip-selfcheck.json -share +# Cython debug symbols +cython_debug/ diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index a72be2cc..37c79bae 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -16,8 +16,8 @@ How to submit an issue Please include the following in your bug reports: -* the version of ics.py you are using; run ``pip freeze | grep ics`` -* the version of Python ``python -v`` +* the version of ics.py you are using; run :command:`pip freeze | grep ics` +* the version of Python :command:`python -v` * the OS you are using Please also include a (preferably minimal) example of the code or @@ -33,6 +33,76 @@ you are solving it. This might save you a lot of time if the maintainers are already working on it or have a specific idea on how the problem should be solved. +Setting up the Development Environment +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There are three Python tools required to develop, test, and release ics.py: + +* `poetry `_ for managing virtualenvs, dependencies, building, and publishing the package. +* `tox `_ for running the testsuite and building the documentation. +* `bump2version `_ to help with making a release. + +Their respective configuration files are :file:`pyproject.toml`, :file:`tox.ini` and :file:`.bumpversion.cfg`. +Install the tools via pip: + +.. code-block:: bash + + $ pip install tox poetry bump2version --user + +.. note:: + If you want to develop using multiple different Python versions, you might want to consider the + `poetry installer `_. + + Poetry will automatically manage a virtualenv that you can use for developing. + By default, it will be located centrally in your home directory (e.g. in :file:`/home/user/.cache/pypoetry/virtualenvs/`). + To make poetry use a :file:`./.venv/` directory within the ics.py folder, use the following config: + + .. code-block:: bash + + $ poetry config virtualenvs.in-project true + +Now you are ready to setup your development environment using the following command: + +.. code-block:: bash + + $ poetry install + +This will create a new virtualenv and install the dependencies for using ics.py. +Furthermore, the current source of the ics.py package will be available similar to running :command:`./setup.py develop`. +To access the virtualenv, use :command:`poetry run python` or :command:`poetry shell`. +The :file:`poetry.lock` file locks the versions of dependencies in the development environment set up by poetry. This ensures that such an environment is the same for everyone. +The file :file:`poetry.lock` is only read by poetry and not included in any distributions. These restrictions don't apply when running :command:`pip install ics`. +As tox manages its own environments and doesn't read the lock file, it installs the latest versions of dependencies for testing. +More details on the poetry side can be found in the `poetry documentation `_. + +If you made some changes and now want to lint your code, run the testsuite, or build the documentation, run tox. +You don't have to worry about which versions in which venvs are installed and whether you're directly testing against the sources or against a built package, tox handles all that for you: + +.. code-block:: bash + + $ tox + +To run a single task and not the whole testsuite, use the ``-e`` flag: + +.. code-block:: bash + + $ tox -e docs + +To get a list of all available tasks, run :command:`tox -av`. + +.. note:: + If you want to run any tasks of tox manually, make sure you have all the dependencies of the task listed in :file:`tox.ini`. + For testing with pytest, this can be done through poetry by installing the ``test`` extra: :command:`poetry install -E test`. + Alternatively, you can also let tox `set up `_ your development environment or re-use one of its test environments: + + .. code-block:: bash + + $ tox -e py38 + $ source .tox/py38/bin/activate + (py38) $ pytest + + This also works without having poetry installed. + If you are fixing a bug ^^^^^^^^^^^^^^^^^^^^^^^ @@ -55,8 +125,8 @@ We will ask you to provide: Last thing ^^^^^^^^^^ -* Please add yourself to ``AUTHORS.rst`` -* and state your changes in ``CHANGELOG.rst``. +* Please add yourself to :file:`AUTHORS.rst` +* and state your changes in :file:`CHANGELOG.rst`. .. note:: Your PR will most likely be squashed in a single commit, authored @@ -66,3 +136,47 @@ Last thing The title of your PR will become the commit message, please craft it with care. + +How to make a new release +------------------------- + +If you want to publish a new release, use the following steps + +.. code-block:: bash + + # Grab the sources and install the dev tools + git clone https://github.com/C4ptainCrunch/ics.py.git && cd ics.py + pip install tox poetry bump2version --user + + # Make sure all the test run + tox && echo "Ready to make a new release" \ + || echo "Please fix all the tests first" + + # Bump the version and make a "0.8.0-dev -> 0.8.0 (release)" commit + bump2version --verbose release + # Build the package + poetry build + # Ensure that the version numbers are consistent + tox --recreate + # Check changelog and amend if necessary + vi CHANGELOG.rst && git commit -i CHANGELOG.rst --amend + # Publish to GitHub + git push && git push --tags + # Publish to PyPi + poetry publish + + # Bump the version again to start development of next version + bump2version --verbose minor # 0.8.0 (release) -> 0.9.0-dev + # Start new changelog + vi CHANGELOG.rst && git commit -i CHANGELOG.rst --amend + # Publish to GitHub + git push && git push --tags + +Please note that bump2version directly makes a commit with the new version if you don't +pass ``--no-commit`` or ``--dry-run``, +but that's no problem as you can easily amend any changes you want to make. +Further things to check: + +* Check GitHub and PyPi release pages for obvious errors +* Build documentation for the tag v{version} on rtfd.org +* Set the default rtfd version to {version} diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 08a5d803..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,11 +0,0 @@ -include *.rst -recursive-include ics *.py -recursive-include ics *.ebnf - -recursive-include logo *.png - -include mypy.ini -include meta.py - -recursive-include tests *.py -recursive-include tests *.ics diff --git a/README.rst b/README.rst index e9ac08ab..79ec1d1b 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ -Ics.py : iCalendar for Humans -============================= +ics.py `0.8.0-dev` : iCalendar for Humans +========================================= `Original repository `_ (GitHub) - `Bugtracker and issues `_ (GitHub) - diff --git a/dev/install-hooks b/dev/install-hooks deleted file mode 100755 index 7a2d79a5..00000000 --- a/dev/install-hooks +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -pushd ./$(git rev-parse --show-cdup) > /dev/null -for file in "post-checkout" "prepare-commit-msg"; do - ln -s "../../dev/$file" ".git/hooks/$file" -done -popd > /dev/null diff --git a/dev/post-checkout b/dev/post-checkout deleted file mode 100755 index fe5e831e..00000000 --- a/dev/post-checkout +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -EXCLUDE="^./ve\|^./ve3|^.git/" -# Delete .pyc files and empty directories from root of project -cd ./$(git rev-parse --show-cdup) - -# Clean-up -find . -name ".DS_Store" -delete - -NUM_PYC_FILES=$( find . -name "*.pyc" | grep -v "$EXCLUDE" |wc -l | tr -d ' ' ) -if [ $NUM_PYC_FILES -gt 0 ]; then - find . -name "*.pyc" -delete - printf "\e[00;31mDeleted $NUM_PYC_FILES .pyc files\e[00m\n" -fi - -NUM_EMPTY_DIRS=$( find . -type d -empty | grep -v "$EXCLUDE" | wc -l | tr -d ' ' ) -if [ $NUM_EMPTY_DIRS -gt 0 ]; then - find . -type d -empty -delete - printf "\e[00;31mDeleted $NUM_EMPTY_DIRS empty directories\e[00m\n" -fi \ No newline at end of file diff --git a/dev/prepare-commit-msg b/dev/prepare-commit-msg deleted file mode 100755 index 13f47935..00000000 --- a/dev/prepare-commit-msg +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh - diff --git a/dev/release-workflow.rst b/dev/release-workflow.rst deleted file mode 100644 index 4eb59325..00000000 --- a/dev/release-workflow.rst +++ /dev/null @@ -1,21 +0,0 @@ -Release HOWTO -============= - -* Update CHANGELOG -* Bump version -* pyroma . -* check-manifest -* `rm dist/*` -* `python3 setup.py egg_info bdist_egg bdist_wheel` -* Test the packages in dist/ -* Upload `twine upload dist/*` -* Check PyPI release page for obvious errors -* `git commit` -* `git tag -a v{version} -m 'Version {version}'` -* Start the new changlog -* Set version to "{version+1}-dev" -* `git commit` -* `git push --tags && git push` -* Update the release on GitHub with the changelog -* Build documentation for the tag v{version} on rtfd.org -* Set the default rtfd version to {version} diff --git a/dev/requirements-doc.txt b/dev/requirements-doc.txt deleted file mode 100644 index 780ae4c7..00000000 --- a/dev/requirements-doc.txt +++ /dev/null @@ -1,4 +0,0 @@ -sphinx -sphinxcontrib-napoleon -sphinx-autodoc-typehints --r ../requirements.txt diff --git a/dev/requirements-test.txt b/dev/requirements-test.txt deleted file mode 100644 index 6377d0a3..00000000 --- a/dev/requirements-test.txt +++ /dev/null @@ -1,6 +0,0 @@ -pytest -pytest-cov -pytest-flakes -pytest-pep8 -pytest-sugar -mypy>=0.770 diff --git a/dev/test b/dev/test deleted file mode 100755 index 33171128..00000000 --- a/dev/test +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -source ve3/bin/activate -python setup.py test -deactivate diff --git a/doc/LICENSE.rst b/doc/LICENSE.rst deleted file mode 120000 index 19cdae75..00000000 --- a/doc/LICENSE.rst +++ /dev/null @@ -1 +0,0 @@ -../LICENSE.rst \ No newline at end of file diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index ee9b3115..00000000 --- a/doc/Makefile +++ /dev/null @@ -1,177 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/icspy.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/icspy.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/icspy" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/icspy" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/doc/event-cmp.rst b/doc/event-cmp.rst index e2c6c216..2c9bc03e 100644 --- a/doc/event-cmp.rst +++ b/doc/event-cmp.rst @@ -65,32 +65,34 @@ attributes. :: >>> e = ics.Event() - >>> e - - >>> str(e) # doctest: +ELLIPSIS - 'BEGIN:VEVENT\r\nDTSTAMP:2020...\r\nUID:...@....org\r\nEND:VEVENT' + >>> e # doctest: +ELLIPSIS + Event(extra=Container('VEVENT', []), extra_params={}, _timespan=EventTimespan(begin_time=None, end_time=None, duration=None, precision='second'), summary=None, uid='...@....org', description=None, location=None, url=None, status=None, created=None, last_modified=None, dtstamp=datetime.datetime(2020, ..., tzinfo=tzutc()), alarms=[], attach=[], classification=None, transparent=None, organizer=None, geo=None, attendees=[], categories=[]) + >>> str(e) + '' + >>> e.serialize() # doctest: +ELLIPSIS + 'BEGIN:VEVENT\r\nUID:...@...org\r\nDTSTAMP:2020...T...Z\r\nEND:VEVENT' >>> import attr, pprint >>> pprint.pprint(attr.asdict(e)) # doctest: +ELLIPSIS - {'_classmethod_args': None, - '_classmethod_kwargs': None, - '_timespan': {'begin_time': None, + {'_timespan': {'begin_time': None, 'duration': None, 'end_time': None, 'precision': 'second'}, 'alarms': [], + 'attach': [], 'attendees': [], 'categories': [], 'classification': None, 'created': None, 'description': None, - 'dtstamp': datetime.datetime(2020, ...), - 'extra': [], + 'dtstamp': datetime.datetime(2020, ..., tzinfo=tzutc()), + 'extra': {'data': [], 'name': 'VEVENT'}, + 'extra_params': {}, 'geo': None, 'last_modified': None, 'location': None, - 'name': None, 'organizer': None, 'status': None, + 'summary': None, 'transparent': None, 'uid': '...@....org', 'url': None} @@ -99,8 +101,8 @@ Ordering -------- TL;DR: ``Event``\ s are ordered by their attributes ``begin``, ``end``, -and ``name``, in that exact order. For ``Todo``\ s the order is ``due``, -``begin``, then ``name``. It doesn’t matter whether ``duration`` is set +and ``summary``, in that exact order. For ``Todo``\ s the order is ``due``, +``begin``, then ``summary``. It doesn’t matter whether ``duration`` is set instead of ``end`` or ``due``, as the effective end / due time will be compared. Instances where an attribute isn’t set will be sorted before instances where the respective attribute is set. Naive ``datetime``\ s @@ -117,7 +119,7 @@ instance as a tuple ``(begin_time, effective_end_time)``: >>> t0 = ics.EventTimespan() >>> t0.cmp_tuple() - TimespanTuple(begin=datetime.datetime(1, 1, 1, 0, 0, tzinfo=tzlocal()), end=datetime.datetime(1, 1, 1, 0, 0, tzinfo=tzlocal())) + TimespanTuple(begin=datetime.datetime(1900, 1, 1, 0, 0, tzinfo=tzlocal()), end=datetime.datetime(1900, 1, 1, 0, 0, tzinfo=tzlocal())) >>> t1 = ics.EventTimespan(begin_time=dt(2020, 2, 20, 20, 20)) >>> t1.cmp_tuple() TimespanTuple(begin=datetime.datetime(2020, 2, 20, 20, 20, tzinfo=tzlocal()), end=datetime.datetime(2020, 2, 20, 20, 20, tzinfo=tzlocal())) @@ -137,14 +139,14 @@ the timespan, as only the effective end time is compared. False The classes ``Event`` and ``Todo`` build on this methods, by appending -their ``name`` to the returned tuple: +their ``summary`` to the returned tuple: :: >>> e11 = ics.Event(timespan=t1) >>> e11.cmp_tuple() (datetime.datetime(2020, 2, 20, 20, 20, tzinfo=tzlocal()), datetime.datetime(2020, 2, 20, 20, 20, tzinfo=tzlocal()), '') - >>> e12 = ics.Event(timespan=t1, name="An Event") + >>> e12 = ics.Event(timespan=t1, summary="An Event") >>> e12.cmp_tuple() (datetime.datetime(2020, 2, 20, 20, 20, tzinfo=tzlocal()), datetime.datetime(2020, 2, 20, 20, 20, tzinfo=tzlocal()), 'An Event') @@ -164,7 +166,7 @@ parameter is set: True >>> ics.Event(timespan=t1) < ics.Event(timespan=t2) True - >>> ics.Event(timespan=t2) < ics.Event(timespan=t2, name="Event Name") + >>> ics.Event(timespan=t2) < ics.Event(timespan=t2, summary="Event Name") True The functions ``__gt__``, ``__le__``, ``__ge__`` all behave similarly by @@ -245,10 +247,10 @@ set: :: >>> import os, time - >>> os.environ['TZ'] = "Europe/Berlin" + >>> os.environ['TZ'] = "Etc/GMT-2" >>> time.tzset() >>> time.tzname - ('CET', 'CEST') + ('+02', '+02') We can easily compare ``datetime`` instances that have an explicit timezone specified: @@ -259,9 +261,11 @@ timezone specified: >>> dt_ny = dt(2020, 2, 20, 20, 20, tzinfo=gettz("America/New York")) >>> dt_utc = dt(2020, 2, 20, 20, 20, tzinfo=tzutc()) >>> dt_local = dt(2020, 2, 20, 20, 20, tzinfo=tzlocal()) + >>> dt_local.tzinfo.tzname(dt_local), dt_local.tzinfo.utcoffset(dt_local).total_seconds() + ('+02', 7200.0) >>> dt_utc < dt_ny True - >>> dt_local < dt_utc # this always holds as tzlocal is Europe/Berlin + >>> dt_local < dt_utc # this always holds as tzlocal is +2:00 (i.e. European Summer Time) True We can also compare naive instances with naive ones, but we can’t @@ -270,7 +274,7 @@ compare naive ones with timezone-aware ones: :: >>> dt_naive = dt(2020, 2, 20, 20, 20) - >>> dt_naive < dt_local + >>> dt_naive < dt_local # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... TypeError: can't compare offset-naive and offset-aware datetimes @@ -285,7 +289,7 @@ which could also be used for comparing instances: >>> (dt_utc.timestamp(), dt_ny.timestamp()) (1582230000.0, 1582248000.0) >>> (dt_local.timestamp(), dt_naive.timestamp()) - (1582226400.0, 1582226400.0) + (1582222800.0, 1582222800.0) This can be become an issue when you e.g. want to iterate all Events of an iCalendar that contains both floating and timezone-aware Events in @@ -336,7 +340,7 @@ compared: False >>> e_local > e_floating False - >>> e_local.begin < e_floating.begin + >>> e_local.begin < e_floating.begin # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... TypeError: can't compare offset-naive and offset-aware datetimes diff --git a/doc/event.rst b/doc/event.rst index 371fe1e7..2cefe1ab 100644 --- a/doc/event.rst +++ b/doc/event.rst @@ -4,7 +4,7 @@ First, let’s import the latest version of ics.py :date: >>> import ics >>> ics.__version__ - '0.8dev' + '0.8.0-dev' We’re also going to create a lot of ``datetime`` and ``timedelta`` objects, so we import them as short-hand aliases ``dt`` and ``td``: @@ -18,8 +18,8 @@ Now, we are ready to create our first event :tada: :: >>> e = ics.Event(begin=dt(2020, 2, 20, 20, 20)) - >>> e - + >>> str(e) + '' We specified no end time or duration for the event, so the event defaults to ending at the same instant it begins. The event is also @@ -32,8 +32,8 @@ the event to set the end time explicitly: :: >>> e.end = dt(2020, 2, 22, 20, 20) - >>> e - + >>> str(e) + '' Now, the duration of the event explicitly shows up and the end time is also marked as being set or “fixed” to a certain instant. If we now set @@ -43,8 +43,8 @@ correspondingly: :: >>> e.duration=td(days=2) - >>> e - + >>> str(e) + '' As we now specified the duration explicitly, the duration is now fixed instead of the end time. This actually makes a big difference when you @@ -54,15 +54,15 @@ now change the start time of the event: >>> e1 = ics.Event(begin=dt(2020, 2, 20, 20, 20), end=dt(2020, 2, 22, 20, 20)) >>> e2 = ics.Event(begin=dt(2020, 2, 20, 20, 20), duration=td(days=2)) - >>> e1 - - >>> e2 - + >>> str(e1) + '' + >>> str(e2) + '' >>> e1.begin = e2.begin = dt(2020, 1, 10, 10, 10) - >>> e1 - - >>> e2 - + >>> str(e1) + '' + >>> str(e2) + '' As we just saw, duration and end can also be passed to the constructor, but both are only allowed when a begin time for the event is specified, @@ -70,7 +70,7 @@ and both can’t be set at the same time: :: - >>> ics.Event(end=dt(2020,2,22,20,20)) + >>> ics.Event(end=dt(2020, 2, 22, 20, 20)) Traceback (most recent call last): ... ValueError: event timespan without begin time can't have end time @@ -78,7 +78,7 @@ and both can’t be set at the same time: Traceback (most recent call last): ... ValueError: timespan without begin time can't have duration - >>> ics.Event(begin=dt(2020,2,20, 20,20), end=dt(2020,2,22,20,20), duration=td(2)) + >>> ics.Event(begin=dt(2020, 2, 20, 20, 20), end=dt(2020, 2, 22, 20, 20), duration=td(2)) Traceback (most recent call last): ... ValueError: can't set duration together with end time @@ -88,7 +88,7 @@ won’t be able to set its duration or end: :: - >>> ics.Event().end = dt(2020,2,22,20,20) + >>> ics.Event().end = dt(2020, 2, 22, 20, 20) Traceback (most recent call last): ... ValueError: event timespan without begin time can't have end time diff --git a/doc/requirements.txt b/doc/requirements.txt deleted file mode 100644 index 78c8bcfe..00000000 --- a/doc/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -sphinx -sphinxcontrib-napoleon -sphinx-autodoc-types diff --git a/ics/__init__.py b/ics/__init__.py deleted file mode 100644 index bc388ca6..00000000 --- a/ics/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -from .__meta__ import (__author__, __copyright__, __license__, # noqa - __title__, __version__) -from .alarm import * # noqa -from .alarm import __all__ as all_alarms -from .attendee import Attendee, Organizer -from .event import Event, Geo -from .grammar.parse import Container, ContentLine -from .icalendar import Calendar -from .timespan import EventTimespan, Timespan, TodoTimespan -from .todo import Todo - -__all__ = [ - *all_alarms, - "Attendee", - "Event", - "Geo", - "Calendar", - "Organizer", - "Timespan", - "EventTimespan", - "TodoTimespan", - "Todo", - "ContentLine", - "Container" -] diff --git a/ics/__meta__.py b/ics/__meta__.py deleted file mode 100644 index 86f3424a..00000000 --- a/ics/__meta__.py +++ /dev/null @@ -1,5 +0,0 @@ -__title__ = "ics" -__version__ = "0.8dev" -__author__ = "Nikita Marchant" -__license__ = "Apache License, Version 2.0" -__copyright__ = "Copyright 2013-2020 Nikita Marchant and individual contributors" diff --git a/ics/alarm/__init__.py b/ics/alarm/__init__.py deleted file mode 100644 index a038f522..00000000 --- a/ics/alarm/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from ics.alarm.audio import AudioAlarm -from ics.alarm.base import BaseAlarm -from ics.alarm.custom import CustomAlarm -from ics.alarm.display import DisplayAlarm -from ics.alarm.email import EmailAlarm -from ics.alarm.none import NoneAlarm - -__all__ = ["BaseAlarm", "AudioAlarm", "DisplayAlarm", "EmailAlarm", "NoneAlarm", "CustomAlarm"] diff --git a/ics/alarm/audio.py b/ics/alarm/audio.py deleted file mode 100644 index d4a1cb8e..00000000 --- a/ics/alarm/audio.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Optional - -import attr -from attr.validators import instance_of, optional as v_optional - -from ics.alarm.base import BaseAlarm -from ics.grammar.parse import ContentLine -from ics.parsers.alarm_parser import AudioAlarmParser -from ics.serializers.alarm_serializer import AudioAlarmSerializer - - -@attr.s(repr=False) -class AudioAlarm(BaseAlarm): - """ - A calendar event VALARM with AUDIO option. - """ - - class Meta: - name = "VALARM" - parser = AudioAlarmParser - serializer = AudioAlarmSerializer - - sound: Optional[ContentLine] = attr.ib(default=None, validator=v_optional(instance_of(ContentLine))) - - @property - def action(self): - return "AUDIO" diff --git a/ics/alarm/base.py b/ics/alarm/base.py deleted file mode 100644 index 52ed95b1..00000000 --- a/ics/alarm/base.py +++ /dev/null @@ -1,68 +0,0 @@ -from abc import ABCMeta, abstractmethod -from datetime import datetime, timedelta -from typing import Any, Type, Union - -import attr -from attr.converters import optional as c_optional -from attr.validators import instance_of, optional as v_optional - -from ics.component import Component, ComponentType -from ics.grammar.parse import Container -from ics.parsers.alarm_parser import BaseAlarmParser -from ics.serializers.alarm_serializer import BaseAlarmSerializer -from ics.utils import ensure_timedelta, get_lines - - -def call_validate_on_inst(inst, attr, value): - inst.validate(attr, value) - - -@attr.s(repr=False) -class BaseAlarm(Component, metaclass=ABCMeta): - """ - A calendar event VALARM base class - """ - - class Meta: - name = "VALARM" - parser = BaseAlarmParser - serializer = BaseAlarmSerializer - - trigger: Union[timedelta, datetime, None] = attr.ib( - default=None, - validator=v_optional(instance_of((timedelta, datetime))) # type: ignore - ) - repeat: int = attr.ib(default=None, validator=call_validate_on_inst) - duration: timedelta = attr.ib(default=None, converter=c_optional(ensure_timedelta), validator=call_validate_on_inst) # type: ignore - - def validate(self, attr=None, value=None): - if self.repeat is not None: - if self.repeat < 0: - raise ValueError("Repeat must be great than or equal to 0.") - if self.duration is None: - raise ValueError( - "A definition of an alarm with a repeating trigger MUST include both the DURATION and REPEAT properties." - ) - - if self.duration is not None and self.duration.total_seconds() < 0: - raise ValueError("Alarm duration timespan must be positive.") - - @classmethod - def _from_container(cls: Type[ComponentType], container: Container, *args: Any, **kwargs: Any) -> ComponentType: - ret = super(BaseAlarm, cls)._from_container(container, *args, **kwargs) # type: ignore - get_lines(ret.extra, "ACTION", keep=False) # Just drop the ACTION line - return ret - - @property - @abstractmethod - def action(self): - """ VALARM action to be implemented by concrete classes - """ - raise NotImplementedError("Base class cannot be instantiated directly") - - def __repr__(self): - value = "{0} trigger:{1}".format(type(self).__name__, self.trigger) - if self.repeat: - value += " repeat:{0} duration:{1}".format(self.repeat, self.duration) - - return "<{0}>".format(value) diff --git a/ics/alarm/custom.py b/ics/alarm/custom.py deleted file mode 100644 index 8a229a41..00000000 --- a/ics/alarm/custom.py +++ /dev/null @@ -1,23 +0,0 @@ -import attr - -from ics.alarm.base import BaseAlarm -from ics.parsers.alarm_parser import CustomAlarmParser -from ics.serializers.alarm_serializer import CustomAlarmSerializer - - -@attr.s(repr=False) -class CustomAlarm(BaseAlarm): - """ - A calendar event VALARM with custom ACTION. - """ - - class Meta: - name = "VALARM" - parser = CustomAlarmParser - serializer = CustomAlarmSerializer - - _action = attr.ib(default=None) - - @property - def action(self): - return self._action diff --git a/ics/alarm/display.py b/ics/alarm/display.py deleted file mode 100644 index 4bec247c..00000000 --- a/ics/alarm/display.py +++ /dev/null @@ -1,23 +0,0 @@ -import attr - -from ics.alarm.base import BaseAlarm -from ics.parsers.alarm_parser import DisplayAlarmParser -from ics.serializers.alarm_serializer import DisplayAlarmSerializer - - -@attr.s(repr=False) -class DisplayAlarm(BaseAlarm): - """ - A calendar event VALARM with DISPLAY option. - """ - - class Meta: - name = "VALARM" - parser = DisplayAlarmParser - serializer = DisplayAlarmSerializer - - display_text: str = attr.ib(default=None) - - @property - def action(self): - return "DISPLAY" diff --git a/ics/alarm/email.py b/ics/alarm/email.py deleted file mode 100644 index a8095dd4..00000000 --- a/ics/alarm/email.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import List - -import attr - -from ics.alarm.base import BaseAlarm -from ics.attendee import Attendee -from ics.parsers.alarm_parser import EmailAlarmParser -from ics.serializers.alarm_serializer import EmailAlarmSerializer -from ics.utils import check_is_instance - - -@attr.s(repr=False) -class EmailAlarm(BaseAlarm): - """ - A calendar event VALARM with Email option. - """ - - class Meta: - name = "VALARM" - parser = EmailAlarmParser - serializer = EmailAlarmSerializer - - subject: str = attr.ib(default=None) - body: str = attr.ib(default=None) - recipients: List[Attendee] = attr.ib(factory=list) # TODO this is a set for Event - - def add_recipient(self, recipient: Attendee): - """ Add an recipient to the recipients list """ - check_is_instance("recipient", recipient, Attendee) - self.recipients.append(recipient) - - @property - def action(self): - return "EMAIL" diff --git a/ics/alarm/none.py b/ics/alarm/none.py deleted file mode 100644 index 44ee0dee..00000000 --- a/ics/alarm/none.py +++ /dev/null @@ -1,18 +0,0 @@ -from ics.alarm.base import BaseAlarm -from ics.parsers.alarm_parser import NoneAlarmParser -from ics.serializers.alarm_serializer import NoneAlarmSerializer - - -class NoneAlarm(BaseAlarm): - """ - A calendar event VALARM with NONE option. - """ - - class Meta: - name = "VALARM" - parser = NoneAlarmParser - serializer = NoneAlarmSerializer - - @property - def action(self): - return "NONE" diff --git a/ics/alarm/utils.py b/ics/alarm/utils.py deleted file mode 100644 index 9ce03296..00000000 --- a/ics/alarm/utils.py +++ /dev/null @@ -1,30 +0,0 @@ -from ics.grammar.parse import ContentLine -from ics.utils import get_lines - - -def get_type_from_action(action_type): - if action_type == "DISPLAY": - from ics.alarm import DisplayAlarm - return DisplayAlarm - elif action_type == "AUDIO": - from ics.alarm import AudioAlarm - return AudioAlarm - elif action_type == "NONE": - from ics.alarm import NoneAlarm - return NoneAlarm - elif action_type == 'EMAIL': - from ics.alarm import EmailAlarm - return EmailAlarm - else: - from ics.alarm import CustomAlarm - return CustomAlarm - - -def get_type_from_container(container): - action_type_lines = get_lines(container, "ACTION", keep=True) - if len(action_type_lines) > 1: - raise ValueError("Too many ACTION parameters in VALARM") - - action_type = action_type_lines[0] - assert isinstance(action_type, ContentLine) - return get_type_from_action(action_type.value) diff --git a/ics/attendee.py b/ics/attendee.py deleted file mode 100644 index 8bbc99cd..00000000 --- a/ics/attendee.py +++ /dev/null @@ -1,98 +0,0 @@ -import warnings -from typing import Dict, List, Optional, Type, TypeVar - -import attr - -from ics.grammar.parse import ContentLine -from ics.parsers.attendee_parser import AttendeeParser, PersonParser -from ics.serializers.attendee_serializer import AttendeeSerializer, PersonSerializer -from ics.utils import escape_string, unescape_string - -PersonType = TypeVar('PersonType', bound='Person') - - -@attr.s -class Person(object): - email: str = attr.ib() - common_name: str = attr.ib(default=None) - dir: Optional[str] = attr.ib(default=None) - sent_by: Optional[str] = attr.ib(default=None) - extra: Dict[str, List[str]] = attr.ib(factory=dict) - - def __attrs_post_init__(self): - if not self.common_name: - self.common_name = self.email - - class Meta: - name = "ABSTRACT-PERSON" - parser = PersonParser - serializer = PersonSerializer - - @classmethod - def parse(cls: Type[PersonType], line: ContentLine) -> PersonType: - email = unescape_string(line.value) - if email.lower().startswith("mailto:"): - email = email[len("mailto:"):] - val = cls(email) - val.populate(line) - return val - - def populate(self, line: ContentLine) -> None: - if line.name != self.Meta.name: - raise ValueError("line isn't an {}".format(self.Meta.name)) - - params = dict(line.params) - for param_name, (parser, options) in self.Meta.parser.get_parsers().items(): - values = params.pop(param_name, []) - if not values and options.required: - if options.default: - values = options.default - default_str = "\\n".join(map(str, options.default)) - message = ("The %s property was not found and is required by the RFC." + - " A default value of \"%s\" has been used instead") % (param_name, default_str) - warnings.warn(message) - else: - raise ValueError('A {} must have at least one {}'.format(line.name, param_name)) - - if not options.multiple and len(values) > 1: - raise ValueError('A {} must have at most one {}'.format(line.name, param_name)) - - if options.multiple: - parser(self, values) # Send a list or empty list - else: - if len(values) == 1: - parser(self, values[0]) # Send the element - else: - parser(self, None) # Send None - - self.extra = params # Store unused lines - - def serialize(self) -> ContentLine: - line = ContentLine(self.Meta.name, params=self.extra, value=escape_string('mailto:%s' % self.email)) - for output in self.Meta.serializer.get_serializers(): - output(self, line) - return line - - def __str__(self) -> str: - """Returns the attendee in an ContentLine format.""" - return str(self.serialize()) - - -class Organizer(Person): - class Meta: - name = 'ORGANIZER' - parser = PersonParser - serializer = PersonSerializer - - -@attr.s -class Attendee(Person): - rsvp: Optional[bool] = attr.ib(default=None) - role: Optional[str] = attr.ib(default=None) - partstat: Optional[str] = attr.ib(default=None) - cutype: Optional[str] = attr.ib(default=None) - - class Meta: - name = 'ATTENDEE' - parser = AttendeeParser - serializer = AttendeeSerializer diff --git a/ics/component.py b/ics/component.py deleted file mode 100644 index d7e2cf0c..00000000 --- a/ics/component.py +++ /dev/null @@ -1,90 +0,0 @@ -import warnings -from typing import Any, Dict, Tuple, Type, TypeVar - -import attr -from attr.validators import instance_of - -from ics.grammar.parse import Container -from ics.parsers.parser import Parser -from ics.serializers.serializer import Serializer -from ics.types import RuntimeAttrValidation -from ics.utils import get_lines - -ComponentType = TypeVar('ComponentType', bound='Component') -PLACEHOLDER_CONTAINER = Container("PLACEHOLDER") - - -@attr.s -class Component(RuntimeAttrValidation): - class Meta: - name = "ABSTRACT" - parser = Parser - serializer = Serializer - - extra: Container = attr.ib(init=False, default=PLACEHOLDER_CONTAINER, validator=instance_of(Container)) - _classmethod_args: Tuple = attr.ib(init=False, default=None, repr=False, eq=False, order=False, hash=False) - _classmethod_kwargs: Dict = attr.ib(init=False, default=None, repr=False, eq=False, order=False, hash=False) - - def __attrs_post_init__(self): - super(Component, self).__attrs_post_init__() - if self.extra is PLACEHOLDER_CONTAINER: - self.extra = Container(self.Meta.name) - - def __init_subclass__(cls): - super().__init_subclass__() - if cls.__str__ != Component.__str__: - raise TypeError("%s may not overwrite %s" % (cls, Component.__str__)) - - @classmethod - def _from_container(cls: Type[ComponentType], container: Container, *args: Any, **kwargs: Any) -> ComponentType: - k = cls() - k._classmethod_args = args - k._classmethod_kwargs = kwargs - k._populate(container) - return k - - def _populate(self, container: Container) -> None: - if container.name != self.Meta.name: - raise ValueError("container isn't an {}".format(self.Meta.name)) - - for line_name, (parser, options) in self.Meta.parser.get_parsers().items(): - lines = get_lines(container, line_name) - if not lines and options.required: - if options.default: - lines = options.default - default_str = "\\n".join(map(str, options.default)) - message = ("The %s property was not found and is required by the RFC." + - " A default value of \"%s\" has been used instead") % (line_name, default_str) - warnings.warn(message) - else: - raise ValueError('A {} must have at least one {}'.format(container.name, line_name)) - - if not options.multiple and len(lines) > 1: - raise ValueError('A {} must have at most one {}'.format(container.name, line_name)) - - if options.multiple: - parser(self, lines) # Send a list or empty list - else: - if len(lines) == 1: - parser(self, lines[0]) # Send the element - else: - parser(self, None) # Send None - - self.extra = container # Store unused lines - - def clone(self): - """ - Returns: - Event: an exact copy of self - """ - return attr.evolve(self) - - def serialize(self) -> Container: - container = self.extra.clone() - for output in self.Meta.serializer.get_serializers(): - output(self, container) - return container - - def __str__(self) -> str: - """Returns the component in an iCalendar format.""" - return str(self.serialize()) diff --git a/ics/grammar/parse.py b/ics/grammar/parse.py deleted file mode 100644 index f44f2d2f..00000000 --- a/ics/grammar/parse.py +++ /dev/null @@ -1,212 +0,0 @@ -import collections -from pathlib import Path -from typing import Dict, List - -import attr -import tatsu -from tatsu.exceptions import FailedToken - -from ics.types import ContainerItem, RuntimeAttrValidation - -grammar_path = Path(__file__).parent.joinpath('contentline.ebnf') - -with open(grammar_path) as fd: - GRAMMAR = tatsu.compile(fd.read()) - - -class ParseError(Exception): - pass - - -@attr.s(repr=False) -class ContentLine(RuntimeAttrValidation): - """ - Represents one property line. - - For example: - - ``FOO;BAR=1:YOLO`` is represented by - - ``ContentLine('FOO', {'BAR': ['1']}, 'YOLO'))`` - """ - - name: str = attr.ib(converter=str.upper) # type: ignore - params: Dict[str, List[str]] = attr.ib(factory=dict) - value: str = attr.ib(default="") - - def __str__(self): - params_str = '' - for pname in self.params: - params_str += ';{}={}'.format(pname, ','.join(self.params[pname])) # TODO ensure escaping? - return "{}{}:{}".format(self.name, params_str, self.value) - - def __repr__(self): - return "".format( - self.name, - len(self.params), - "s" if len(self.params) > 1 else "", - self.value, - ) - - def __getitem__(self, item): - return self.params[item] - - def __setitem__(self, item, *values): - self.params[item] = list(values) - - @classmethod - def parse(cls, line): - """Parse a single iCalendar-formatted line into a ContentLine""" - if "\n" in line or "\r" in line: - raise ValueError("ContentLine can only contain escaped newlines") - try: - ast = GRAMMAR.parse(line) - except FailedToken: - raise ParseError() - else: - return cls.interpret_ast(ast) - - @classmethod - def interpret_ast(cls, ast): - name = ''.join(ast['name']) - value = ''.join(ast['value']) - params = {} - for param_ast in ast.get('params', []): - param_name = ''.join(param_ast["name"]) - param_values = [''.join(x) for x in param_ast["values_"]] - params[param_name] = param_values - return cls(name, params, value) - - def clone(self): - """Makes a copy of itself""" - return attr.evolve(self) - - -class Container(List[ContainerItem]): - """Represents an iCalendar object. - Contains a list of ContentLines or Containers. - - Args: - - name: the name of the object (VCALENDAR, VEVENT etc.) - items: Containers or ContentLines - """ - - def __init__(self, name: str, *items: ContainerItem): - self.check_items(*items) - super(Container, self).__init__(items) - self.name = name - - def __str__(self): - name = self.name - ret = ['BEGIN:' + name] - for line in self: - ret.append(str(line)) - ret.append('END:' + name) - return "\r\n".join(ret) - - def __repr__(self): - return "" \ - .format(self.name, len(self), "s" if len(self) > 1 else "") - - @classmethod - def parse(cls, name, tokenized_lines): - items = [] - for line in tokenized_lines: - if line.name == 'BEGIN': - items.append(cls.parse(line.value, tokenized_lines)) - elif line.name == 'END': - if line.value != name: - raise ParseError( - "Expected END:{}, got END:{}".format(name, line.value)) - break - else: - items.append(line) - return cls(name, *items) - - def clone(self): - """Makes a copy of itself""" - return self.__class__(self.name, *self) - - def check_items(self, *items): - from ics.utils import check_is_instance - if len(items) == 1: - check_is_instance("item", items[0], (ContentLine, Container)) - else: - for nr, item in enumerate(items): - check_is_instance("item %s" % nr, item, (ContentLine, Container)) - - def __setitem__(self, index, value): - self.check_items(value) - super(Container, self).__setitem__(index, value) - - def insert(self, index, value): - self.check_items(value) - super(Container, self).insert(index, value) - - def append(self, value): - self.check_items(value) - super(Container, self).append(value) - - def extend(self, values): - self.check_items(*values) - super(Container, self).extend(values) - - def __add__(self, values): - container = type(self)(self.name) - container.extend(self) - container.extend(values) - return container - - def __iadd__(self, values): - self.extend(values) - return self - - -def unfold_lines(physical_lines): - if not isinstance(physical_lines, collections.abc.Iterable): - raise ParseError('Parameter `physical_lines` must be an iterable') - current_line = '' - for line in physical_lines: - if len(line.strip()) == 0: - continue - elif not current_line: - current_line = line.strip('\r') - elif line[0] in (' ', '\t'): - current_line += line[1:].strip('\r') - else: - yield current_line - current_line = line.strip('\r') - if current_line: - yield current_line - - -def tokenize_line(unfolded_lines): - for line in unfolded_lines: - yield ContentLine.parse(line) - - -def parse(tokenized_lines): - # tokenized_lines must be an iterator, so that Container.parse can consume/steal lines - tokenized_lines = iter(tokenized_lines) - res = [] - for line in tokenized_lines: - if line.name == 'BEGIN': - res.append(Container.parse(line.value, tokenized_lines)) - else: - res.append(line) - return res - - -def lines_to_container(lines): - return parse(tokenize_line(unfold_lines(lines))) - - -def string_to_container(txt): - return lines_to_container(txt.splitlines()) - - -def calendar_string_to_containers(string): - if not isinstance(string, str): - raise TypeError("Expecting a string") - return string_to_container(string) diff --git a/ics/parsers/alarm_parser.py b/ics/parsers/alarm_parser.py deleted file mode 100644 index 16cc41d1..00000000 --- a/ics/parsers/alarm_parser.py +++ /dev/null @@ -1,70 +0,0 @@ -import warnings -from typing import TYPE_CHECKING, List - -from ics.attendee import Attendee -from ics.grammar.parse import ContentLine -from ics.parsers.parser import Parser, option -from ics.utils import parse_datetime, parse_duration, unescape_string - -if TYPE_CHECKING: - from ics.alarm import BaseAlarm, AudioAlarm, DisplayAlarm, EmailAlarm, CustomAlarm - - -class BaseAlarmParser(Parser): - @option(required=True) - def parse_trigger(alarm: "BaseAlarm", line: ContentLine): - if line.params.get("VALUE", [""])[0] == "DATE-TIME": - alarm.trigger = parse_datetime(line) - elif line.params.get("VALUE", ["DURATION"])[0] == "DURATION": - alarm.trigger = parse_duration(line.value) - else: - warnings.warn( - "ics.py encountered a TRIGGER of unknown type '%s'. It has been ignored." - % line.params["VALUE"][0] - ) - - def parse_duration(alarm: "BaseAlarm", line: ContentLine): - if line: - alarm.duration = parse_duration(line.value) - - def parse_repeat(alarm: "BaseAlarm", line: ContentLine): - if line: - alarm.repeat = int(line.value) - - -class CustomAlarmParser(BaseAlarmParser): - @option(required=True) - def parse_action(alarm: "CustomAlarm", line: ContentLine): - if line: - alarm._action = line.value - - -class AudioAlarmParser(BaseAlarmParser): - def parse_attach(alarm: "AudioAlarm", line: ContentLine): - if line: - alarm.sound = line - - -class DisplayAlarmParser(BaseAlarmParser): - @option(required=True) - def parse_description(alarm: "DisplayAlarm", line: ContentLine): - alarm.display_text = unescape_string(line.value) if line else None - - -class EmailAlarmParser(BaseAlarmParser): - @option(required=True) - def parse_description(alarm: "EmailAlarm", line: ContentLine): - alarm.body = unescape_string(line.value) if line else None - - @option(required=True) - def parse_summary(alarm: "EmailAlarm", line: ContentLine): - alarm.subject = unescape_string(line.value) if line else None - - @option(required=True, multiple=True) - def parse_attendee(alarm: "EmailAlarm", lines: List[ContentLine]): - for line in lines: - alarm.recipients.append(Attendee.parse(line)) - - -class NoneAlarmParser(BaseAlarmParser): - pass diff --git a/ics/parsers/attendee_parser.py b/ics/parsers/attendee_parser.py deleted file mode 100644 index 7a9b9ba8..00000000 --- a/ics/parsers/attendee_parser.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import TYPE_CHECKING - -from ics.parsers.parser import Parser -from ics.utils import unescape_string - -if TYPE_CHECKING: - from ics.attendee import Person, Attendee - - -class PersonParser(Parser): - def parse_cn(person: "Person", value): - if value: - person.common_name = unescape_string(value) - - def parse_dir(person: "Person", value): - if value: - person.dir = unescape_string(value) - - def parse_sent_by(person: "Person", value): - if value: - person.sent_by = unescape_string(value) - - -class AttendeeParser(PersonParser): - def parse_rsvp(attendee: "Attendee", value): - if value: - attendee.rsvp = bool(value) - - def parse_role(attendee: "Attendee", value): - if value: - attendee.role = unescape_string(value) - - def parse_partstat(attendee: "Attendee", value): - if value: - attendee.partstat = unescape_string(value) - - def parse_cutype(attendee: "Attendee", value): - if value: - attendee.cutype = unescape_string(value) diff --git a/ics/parsers/event_parser.py b/ics/parsers/event_parser.py deleted file mode 100644 index af75cf68..00000000 --- a/ics/parsers/event_parser.py +++ /dev/null @@ -1,104 +0,0 @@ -import re -from typing import List, TYPE_CHECKING - -from ics.alarm.utils import get_type_from_container -from ics.attendee import Attendee, Organizer -from ics.grammar.parse import ContentLine -from ics.parsers.parser import Parser, option -from ics.utils import iso_precision, parse_datetime, parse_duration, unescape_string - -if TYPE_CHECKING: - from ics.event import Event - - -class EventParser(Parser): - def parse_dtstamp(event: "Event", line: ContentLine): - if line: - # get the dict of vtimezones passed to the classmethod - tz_dict = event._classmethod_kwargs["tz"] - event.dtstamp = parse_datetime(line, tz_dict) - - def parse_created(event: "Event", line: ContentLine): - if line: - tz_dict = event._classmethod_kwargs["tz"] - event.created = parse_datetime(line, tz_dict) - - def parse_last_modified(event: "Event", line: ContentLine): - if line: - tz_dict = event._classmethod_kwargs["tz"] - event.last_modified = parse_datetime(line, tz_dict) - - def parse1_dtstart(event: "Event", line: ContentLine): - if line: - # get the dict of vtimezones passed to the classmethod - tz_dict = event._classmethod_kwargs["tz"] - event._timespan = event._timespan.replace( - begin_time=parse_datetime(line, tz_dict), - precision=iso_precision(line.value) - ) - - def parse2_duration(event: "Event", line: ContentLine): - if line: - event._timespan = event._timespan.replace( - duration=parse_duration(line.value) - ) - - def parse3_dtend(event: "Event", line: ContentLine): - if line: - tz_dict = event._classmethod_kwargs["tz"] - event._timespan = event._timespan.replace( - end_time=parse_datetime(line, tz_dict) - ) - - def parse_summary(event: "Event", line: ContentLine): - event.name = unescape_string(line.value) if line else None - - def parse_organizer(event: "Event", line: ContentLine): - event.organizer = Organizer.parse(line) if line else None - - @option(multiple=True) - def parse_attendee(event: "Event", lines: List[ContentLine]): - for line in lines: - event.attendees.append(Attendee.parse(line)) - - def parse_description(event: "Event", line: ContentLine): - event.description = unescape_string(line.value) if line else None - - def parse_location(event: "Event", line: ContentLine): - event.location = unescape_string(line.value) if line else None - - def parse_geo(event: "Event", line: ContentLine): - if line: - latitude, _, longitude = unescape_string(line.value).partition(";") - event.geo = float(latitude), float(longitude) - - def parse_url(event: "Event", line: ContentLine): - event.url = unescape_string(line.value) if line else None - - def parse_transp(event: "Event", line: ContentLine): - if line and line.value in ["TRANSPARENT", "OPAQUE"]: - event.transparent = line.value == "TRANSPARENT" - - # TODO : make uid required ? - def parse_uid(event: "Event", line: ContentLine): - if line: - event.uid = line.value - - @option(multiple=True) - def parse_valarm(event, lines: List[ContentLine]): - event.alarms = [get_type_from_container(x)._from_container(x) for x in lines] - - def parse_status(event: "Event", line: ContentLine): - if line: - event.status = line.value - - def parse_class(event: "Event", line: ContentLine): - if line: - event.classification = line.value - - def parse_categories(event: "Event", line: ContentLine): - event.categories = set() - if line: - # In the regular expression: Only match unquoted commas. - for cat in re.split("(? Dict[str, Tuple[Callable, ParserOption]]: - methods = sorted( - (method_name, getattr(cls, method_name)) - for method_name in dir(cls) - if callable(getattr(cls, method_name)) - ) - parsers = [ - (method_name, method_callable) - for (method_name, method_callable) in methods - if re.match("^parse[0-9]*_", method_name) - ] - return OrderedDict( - ( - method_name.split("_", 1)[1].upper().replace("_", "-"), ( - method_callable, - getattr(method_callable, "options", ParserOption()), - ) - ) - for (method_name, method_callable) in parsers - ) - - -def option( - required: bool = False, - multiple: bool = False, - default: Optional[List[ContentLine]] = None, -) -> Callable: - def decorator(fn): - fn.options = ParserOption(required, multiple, default) - return fn - - return decorator diff --git a/ics/parsers/todo_parser.py b/ics/parsers/todo_parser.py deleted file mode 100644 index 38cf93d4..00000000 --- a/ics/parsers/todo_parser.py +++ /dev/null @@ -1,87 +0,0 @@ -from typing import List, TYPE_CHECKING - -from ics.alarm.utils import get_type_from_container -from ics.grammar.parse import ContentLine -from ics.parsers.parser import Parser, option -from ics.utils import parse_datetime, parse_duration, unescape_string - -if TYPE_CHECKING: - from ics.todo import Todo - - -class TodoParser(Parser): - @option(required=True) - def parse_dtstamp(todo: "Todo", line: ContentLine): - if line: - # get the dict of vtimezones passed to the classmethod - tz_dict = todo._classmethod_kwargs["tz"] - todo.dtstamp = parse_datetime(line, tz_dict) - - def parse_last_modified(todo: "Todo", line: ContentLine): - if line: - tz_dict = todo._classmethod_kwargs["tz"] - todo.last_modified = parse_datetime(line, tz_dict) - - @option(required=True) - def parse_uid(todo: "Todo", line: ContentLine): - if line: - todo.uid = line.value - - def parse_completed(todo: "Todo", line: ContentLine): - if line: - # get the dict of vtimezones passed to the classmethod - tz_dict = todo._classmethod_kwargs["tz"] - todo.completed = parse_datetime(line, tz_dict) - - def parse_created(todo: "Todo", line: ContentLine): - if line: - # get the dict of vtimezones passed to the classmethod - tz_dict = todo._classmethod_kwargs["tz"] - todo.created = parse_datetime(line, tz_dict) - - def parse_description(todo: "Todo", line: ContentLine): - todo.description = unescape_string(line.value) if line else None - - def parse_location(todo: "Todo", line: ContentLine): - todo.location = unescape_string(line.value) if line else None - - def parse_percent_complete(todo: "Todo", line: ContentLine): - todo.percent = int(line.value) if line else None - - def parse_priority(todo: "Todo", line: ContentLine): - todo.priority = int(line.value) if line else None - - def parse_summary(todo: "Todo", line: ContentLine): - todo.name = unescape_string(line.value) if line else None - - def parse_url(todo: "Todo", line: ContentLine): - todo.url = unescape_string(line.value) if line else None - - def parse_dtstart(todo: "Todo", line: ContentLine): - if line: - # get the dict of vtimezones passed to the classmethod - tz_dict = todo._classmethod_kwargs["tz"] - todo._timespan = todo._timespan.replace( - begin_time=parse_datetime(line, tz_dict) - ) - - def parse_duration(todo: "Todo", line: ContentLine): - if line: - todo._timespan = todo._timespan.replace( - duration=parse_duration(line.value) - ) - - def parse_due(todo: "Todo", line: ContentLine): - if line: - tz_dict = todo._classmethod_kwargs["tz"] - todo._timespan = todo._timespan.replace( - end_time=parse_datetime(line, tz_dict) - ) - - @option(multiple=True) - def parse_valarm(todo: "Todo", lines: List[ContentLine]): - todo.alarms = [get_type_from_container(x)._from_container(x) for x in lines] - - def parse_status(todo: "Todo", line: ContentLine): - if line: - todo.status = line.value diff --git a/ics/serializers/alarm_serializer.py b/ics/serializers/alarm_serializer.py deleted file mode 100644 index effe83ce..00000000 --- a/ics/serializers/alarm_serializer.py +++ /dev/null @@ -1,68 +0,0 @@ -from datetime import timedelta - -from ics.grammar.parse import ContentLine -from ics.serializers.serializer import Serializer -from ics.utils import escape_string, serialize_datetime_to_contentline, serialize_duration - - -class BaseAlarmSerializer(Serializer): - def serialize_trigger(alarm, container): - if not alarm.trigger: - raise ValueError("Alarm must have a trigger") - - if isinstance(alarm.trigger, timedelta): - representation = serialize_duration(alarm.trigger) - container.append(ContentLine("TRIGGER", value=representation)) - else: - cl = serialize_datetime_to_contentline("TRIGGER", alarm.trigger) - cl.params["VALUE"] = ["DATE-TIME"] - container.append(cl) - - def serialize_duration(alarm, container): - if alarm.duration: - representation = serialize_duration(alarm.duration) - container.append(ContentLine("DURATION", value=representation)) - - def serialize_repeat(alarm, container): - if alarm.repeat: - container.append(ContentLine("REPEAT", value=alarm.repeat)) - - def serialize_action(alarm, container): - container.append(ContentLine("ACTION", value=alarm.action)) - - -class CustomAlarmSerializer(BaseAlarmSerializer): - pass - - -class AudioAlarmSerializer(BaseAlarmSerializer): - def serialize_attach(alarm, container): - if alarm.sound: - container.append(alarm.sound) - - -class DisplayAlarmSerializer(BaseAlarmSerializer): - def serialize_description(alarm, container): - container.append( - ContentLine("DESCRIPTION", value=escape_string(alarm.display_text or "")) - ) - - -class EmailAlarmSerializer(BaseAlarmSerializer): - def serialize_body(alarm, container): - container.append( - ContentLine("DESCRIPTION", value=escape_string(alarm.body or "")) - ) - - def serialize_subject(alarm, container): - container.append( - ContentLine("SUMMARY", value=escape_string(alarm.subject or "")) - ) - - def serialize_recipients(alarm, container): - for attendee in alarm.recipients: - container.append(attendee.serialize()) - - -class NoneAlarmSerializer(BaseAlarmSerializer): - pass diff --git a/ics/serializers/attendee_serializer.py b/ics/serializers/attendee_serializer.py deleted file mode 100644 index ddd97df7..00000000 --- a/ics/serializers/attendee_serializer.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import TYPE_CHECKING - -from ics.grammar.parse import ContentLine -from ics.serializers.serializer import Serializer -from ics.utils import escape_string - -if TYPE_CHECKING: - from ics.attendee import Person, Attendee - - -class PersonSerializer(Serializer): - def serialize_cn(person: "Person", line: ContentLine): - if person.common_name: - line.params["CN"] = [escape_string(person.common_name)] - - def serialize_dir(person: "Person", line: ContentLine): - if person.dir: - line.params["DIR"] = [escape_string(person.dir)] - - def serialize_sent_by(person: "Person", line: ContentLine): - if person.sent_by: - line.params["SENT-BY"] = [escape_string(person.sent_by)] - - -class AttendeeSerializer(PersonSerializer): - def serialize_rsvp(attendee: "Attendee", line: ContentLine): - if attendee.rsvp is not None: - line.params["RSVP"] = [str(attendee.rsvp).upper()] - - def serialize_role(attendee: "Attendee", line: ContentLine): - if attendee.role: - line.params["ROLE"] = [escape_string(attendee.role)] - - def serialize_partstat(attendee: "Attendee", line: ContentLine): - if attendee.partstat: - line.params["PARTSTAT"] = [escape_string(attendee.partstat)] - - def serialize_cutype(attendee: "Attendee", line: ContentLine): - if attendee.cutype: - line.params["CUTYPE"] = [escape_string(attendee.cutype)] diff --git a/ics/serializers/event_serializer.py b/ics/serializers/event_serializer.py deleted file mode 100644 index 86460554..00000000 --- a/ics/serializers/event_serializer.py +++ /dev/null @@ -1,131 +0,0 @@ -from typing import TYPE_CHECKING - -from ics.attendee import Attendee, Organizer -from ics.grammar.parse import ContentLine -from ics.serializers.serializer import Serializer -from ics.utils import (escape_string, serialize_date, serialize_datetime_to_contentline, serialize_duration, uid_gen) - -if TYPE_CHECKING: - from ics.event import Event - from ics.grammar.parse import Container - - -class EventSerializer(Serializer): - def serialize_dtstamp(event: "Event", container: "Container"): - container.append(serialize_datetime_to_contentline("DTSTAMP", event.dtstamp)) - - def serialize_created(event: "Event", container: "Container"): - if event.created: - container.append(serialize_datetime_to_contentline("CREATED", event.created)) - - def serialize_last_modified(event: "Event", container: "Container"): - if event.last_modified: - container.append(serialize_datetime_to_contentline("LAST-MODIFIED", event.last_modified)) - - def serialize_start(event: "Event", container: "Container"): - if event.begin: - if not event.all_day: - container.append(serialize_datetime_to_contentline("DTSTART", event.begin)) - else: - container.append( - ContentLine( - "DTSTART", - params={"VALUE": ["DATE"]}, - value=serialize_date(event.begin), - ) - ) - - def serialize_end(event: "Event", container: "Container"): - if event.end_representation == "end": - end = event.end - assert end is not None - if not event.all_day: - container.append(serialize_datetime_to_contentline("DTEND", end)) - else: - container.append( - ContentLine( - "DTSTART", - params={"VALUE": ["DATE"]}, - value=serialize_date(end), - ) - ) - - def serialize_duration(event: "Event", container: "Container"): - if event.end_representation == "duration": - duration = event.duration - assert duration is not None - container.append(ContentLine("DURATION", value=serialize_duration(duration))) - - def serialize_summary(event: "Event", container: "Container"): - if event.name: - container.append(ContentLine("SUMMARY", value=escape_string(event.name))) - - def serialize_organizer(event: "Event", container: "Container"): - if event.organizer: - organizer = event.organizer - if isinstance(organizer, str): - organizer = Organizer(organizer) - container.append(organizer.serialize()) - - def serialize_attendee(event: "Event", container: "Container"): - for attendee in event.attendees: - if isinstance(attendee, str): - attendee = Attendee(attendee) - container.append(attendee.serialize()) - - def serialize_description(event: "Event", container: "Container"): - if event.description: - container.append( - ContentLine("DESCRIPTION", value=escape_string(event.description)) - ) - - def serialize_location(event: "Event", container: "Container"): - if event.location: - container.append( - ContentLine("LOCATION", value=escape_string(event.location)) - ) - - def serialize_geo(event: "Event", container: "Container"): - if event.geo: - container.append(ContentLine("GEO", value="%f;%f" % event.geo)) - - def serialize_url(event: "Event", container: "Container"): - if event.url: - container.append(ContentLine("URL", value=escape_string(event.url))) - - def serialize_transparent(event: "Event", container: "Container"): - if event.transparent is None: - return - if event.transparent: - container.append(ContentLine("TRANSP", value=escape_string("TRANSPARENT"))) - else: - container.append(ContentLine("TRANSP", value=escape_string("OPAQUE"))) - - def serialize_uid(event: "Event", container: "Container"): - if event.uid: - uid = event.uid - else: - uid = uid_gen() - - container.append(ContentLine("UID", value=uid)) - - def serialize_alarm(event: "Event", container: "Container"): - for alarm in event.alarms: - container.append(alarm.serialize()) - - def serialize_status(event: "Event", container: "Container"): - if event.status: - container.append(ContentLine("STATUS", value=event.status)) - - def serialize_class(event: "Event", container: "Container"): - if event.classification: - container.append(ContentLine("CLASS", value=event.classification)) - - def serialize_categories(event: "Event", container: "Container"): - if event.categories: - container.append( - ContentLine( - "CATEGORIES", - value=",".join([escape_string(s) for s in event.categories]), - ) - ) diff --git a/ics/serializers/icalendar_serializer.py b/ics/serializers/icalendar_serializer.py deleted file mode 100644 index 70177de1..00000000 --- a/ics/serializers/icalendar_serializer.py +++ /dev/null @@ -1,31 +0,0 @@ -from ics.grammar.parse import ContentLine -from ics.serializers.serializer import Serializer - - -class CalendarSerializer(Serializer): - def serialize_0version(calendar, container): # 0version will be sorted first - container.append(ContentLine("VERSION", value="2.0")) - - def serialize_1prodid(calendar, container): # 1prodid will be sorted second - if calendar.prodid: - prodid = calendar.prodid - else: - prodid = "ics.py - http://git.io/lLljaA" - - container.append(ContentLine("PRODID", value=prodid)) - - def serialize_calscale(calendar, container): - if calendar.scale: - container.append(ContentLine("CALSCALE", value=calendar.scale.upper())) - - def serialize_method(calendar, container): - if calendar.method: - container.append(ContentLine("METHOD", value=calendar.method.upper())) - - def serialize_event(calendar, container): - for event in calendar.events: - container.append(event.serialize()) - - def serialize_todo(calendar, container): - for todo in calendar.todos: - container.append(todo.serialize()) diff --git a/ics/serializers/serializer.py b/ics/serializers/serializer.py deleted file mode 100644 index aa9ae43b..00000000 --- a/ics/serializers/serializer.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Callable, List - - -class Serializer: - @classmethod - def get_serializers(cls) -> List[Callable]: - methods = [ - (method_name, getattr(cls, method_name)) - for method_name in dir(cls) - if callable(getattr(cls, method_name)) - ] - return sorted([ - method_callable - for (method_name, method_callable) in methods - if method_name.startswith("serialize_") - ], key=lambda x: x.__name__) diff --git a/ics/serializers/todo_serializer.py b/ics/serializers/todo_serializer.py deleted file mode 100644 index 74e3a440..00000000 --- a/ics/serializers/todo_serializer.py +++ /dev/null @@ -1,87 +0,0 @@ -from typing import TYPE_CHECKING - -from ics.grammar.parse import Container, ContentLine -from ics.serializers.serializer import Serializer -from ics.utils import (escape_string, serialize_datetime_to_contentline, serialize_duration, uid_gen) - -if TYPE_CHECKING: - from ics.todo import Todo - - -class TodoSerializer(Serializer): - def serialize_dtstamp(todo: "Todo", container: "Container"): - container.append(serialize_datetime_to_contentline("DTSTAMP", todo.dtstamp)) - - def serialize_created(todo: "Todo", container: "Container"): - if todo.created: - container.append(serialize_datetime_to_contentline("CREATED", todo.created)) - - def serialize_last_modified(todo: "Todo", container: "Container"): - if todo.last_modified: - container.append(serialize_datetime_to_contentline("LAST-MODIFIED", todo.last_modified)) - - def serialize_uid(todo: "Todo", container: Container): - if todo.uid: - uid = todo.uid - else: - uid = uid_gen() - - container.append(ContentLine("UID", value=uid)) - - def serialize_completed(todo: "Todo", container: Container): - if todo.completed: - container.append( - serialize_datetime_to_contentline("COMPLETED", todo.completed) - ) - - def serialize_description(todo: "Todo", container: Container): - if todo.description: - container.append( - ContentLine("DESCRIPTION", value=escape_string(todo.description)) - ) - - def serialize_location(todo: "Todo", container: Container): - if todo.location: - container.append( - ContentLine("LOCATION", value=escape_string(todo.location)) - ) - - def serialize_percent(todo: "Todo", container: Container): - if todo.percent is not None: - container.append(ContentLine("PERCENT-COMPLETE", value=str(todo.percent))) - - def serialize_priority(todo: "Todo", container: Container): - if todo.priority is not None: - container.append(ContentLine("PRIORITY", value=str(todo.priority))) - - def serialize_summary(todo: "Todo", container: Container): - if todo.name: - container.append(ContentLine("SUMMARY", value=escape_string(todo.name))) - - def serialize_url(todo: "Todo", container: Container): - if todo.url: - container.append(ContentLine("URL", value=escape_string(todo.url))) - - def serialize_start(todo: "Todo", container: Container): - if todo.begin: - container.append(serialize_datetime_to_contentline("DTSTART", todo.begin)) - - def serialize_due(todo: "Todo", container: Container): - if todo.due_representation == "end": - due = todo.due - assert due is not None - container.append(serialize_datetime_to_contentline("DUE", due)) - - def serialize_duration(todo: "Todo", container: Container): - if todo.due_representation == "duration": - duration = todo.duration - assert duration is not None - container.append(ContentLine("DURATION", value=serialize_duration(duration))) - - def serialize_alarm(todo: "Todo", container: Container): - for alarm in todo.alarms: - container.append(alarm.serialize()) - - def serialize_status(todo: "Todo", container: Container): - if todo.status: - container.append(ContentLine("STATUS", value=todo.status)) diff --git a/ics/tools.py b/ics/tools.py deleted file mode 100644 index d178de87..00000000 --- a/ics/tools.py +++ /dev/null @@ -1,26 +0,0 @@ -import re - - -def striphtml(data): - p = re.compile(r'<.*?>') - return p.sub('', data) - - -def validate(string): - import requests - payload = {'snip': string} - ret = requests.post('http://severinghaus.org/projects/icv/', data=payload) - if 'Sorry, your calendar could not be parsed.' in ret.text: - i = ret.text.index('
') - j = ret.text[i:].index('
') - usefull = ret.text[i:i + j] - usefull_clean = striphtml(usefull) - lines = usefull_clean.split('\n') - lines_clean = map(lambda x: x.strip(), lines) - lines_no_empty = filter(lambda x: x != '', lines_clean) - return '\n'.join(lines_no_empty) - - elif 'Congratulations; your calendar validated!' in ret.text: - return True - else: - return None diff --git a/ics/types.py b/ics/types.py deleted file mode 100644 index 4480fc9e..00000000 --- a/ics/types.py +++ /dev/null @@ -1,93 +0,0 @@ -from datetime import date, datetime, timedelta, tzinfo -from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Union, overload - -import attr - -if TYPE_CHECKING: - # noinspection PyUnresolvedReferences - from ics.event import Event, CalendarEntryAttrs - # noinspection PyUnresolvedReferences - from ics.todo import Todo - # noinspection PyUnresolvedReferences - from ics.timespan import Timespan - # noinspection PyUnresolvedReferences - from ics.grammar.parse import ContentLine, Container - -__all__ = [ - "ContainerItem", "ContainerList", "DatetimeLike", "OptionalDatetimeLike", "TimespanOrBegin", "EventOrTimespan", - "EventOrTimespanOrInstant", "TodoOrTimespan", "TodoOrTimespanOrInstant", "CalendarEntryOrTimespan", - "CalendarEntryOrTimespanOrInstant", "OptionalTZDict", "get_timespan_if_calendar_entry" -] - -ContainerItem = Union["ContentLine", "Container"] -ContainerList = List[ContainerItem] - -DatetimeLike = Union[Tuple, Dict, datetime, date] -OptionalDatetimeLike = Union[Tuple, Dict, datetime, date, None] -TimedeltaLike = Union[Tuple, Dict, timedelta] -OptionalTimedeltaLike = Union[Tuple, Dict, timedelta, None] - -TimespanOrBegin = Union[datetime, date, "Timespan"] -EventOrTimespan = Union["Event", "Timespan"] -EventOrTimespanOrInstant = Union["Event", "Timespan", datetime] -TodoOrTimespan = Union["Todo", "Timespan"] -TodoOrTimespanOrInstant = Union["Todo", "Timespan", datetime] -CalendarEntryOrTimespan = Union["CalendarEntryAttrs", "Timespan"] -CalendarEntryOrTimespanOrInstant = Union["CalendarEntryAttrs", "Timespan", datetime] - -OptionalTZDict = Optional[Dict[str, tzinfo]] - - -@overload -def get_timespan_if_calendar_entry(value: CalendarEntryOrTimespan) -> "Timespan": - ... - - -@overload -def get_timespan_if_calendar_entry(value: datetime) -> datetime: - ... - - -@overload -def get_timespan_if_calendar_entry(value: None) -> None: - ... - - -def get_timespan_if_calendar_entry(value): - from ics.event import CalendarEntryAttrs # noqa - - if isinstance(value, CalendarEntryAttrs): - return value._timespan - else: - return value - - -@attr.s -class RuntimeAttrValidation(object): - """ - Mixin that automatically calls the converters and validators of `attr` attributes. - The library itself only calls these in the generated `__init__` method, with - this mixin they are also called when later (re-)assigning an attribute, which - is handled by `__setattr__`. This makes setting attributes as versatile as specifying - them as init parameters and also ensures that the guarantees of validators are - preserved even after creation of the object, at a small runtime cost. - """ - - def __attrs_post_init__(self): - self.__post_init__ = True - - def __setattr__(self, key, value): - if getattr(self, "__post_init__", None): - cls = self.__class__ # type: Any - if not getattr(cls, "__attr_fields__", None): - cls.__attr_fields__ = attr.fields_dict(cls) - try: - field = cls.__attr_fields__[key] - except KeyError: - pass - else: # when no KeyError was thrown - if field.converter is not None: - value = field.converter(value) - if field.validator is not None: - field.validator(self, field, value) - super(RuntimeAttrValidation, self).__setattr__(key, value) diff --git a/ics/utils.py b/ics/utils.py deleted file mode 100644 index 9f212ad1..00000000 --- a/ics/utils.py +++ /dev/null @@ -1,428 +0,0 @@ -from datetime import date, datetime, time, timedelta, timezone -from typing import Generator, Optional, overload -from uuid import uuid4 - -from dateutil.tz import UTC as dateutil_tzutc, gettz - -from ics.grammar.parse import Container, ContentLine, ParseError -from ics.types import ContainerList, DatetimeLike, OptionalTZDict, TimedeltaLike - -datetime_tzutc = timezone.utc -midnight = time() -DATE_FORMATS = { - 6: "%Y%m", - 8: "%Y%m%d", - 11: "%Y%m%dT%H", - 13: "%Y%m%dT%H%M", - 15: "%Y%m%dT%H%M%S" -} -TIMEDELTA_CACHE = { - 0: timedelta(), - "day": timedelta(days=1), - "second": timedelta(seconds=1) -} -MAX_TIMEDELTA_NEARLY_ZERO = timedelta(seconds=1) / 2 - - -def timedelta_nearly_zero(td: timedelta) -> bool: - return -MAX_TIMEDELTA_NEARLY_ZERO <= td <= MAX_TIMEDELTA_NEARLY_ZERO - - -@overload -def parse_datetime(time_container: None, available_tz: OptionalTZDict = None) -> None: ... - - -@overload -def parse_datetime(time_container: ContentLine, available_tz: OptionalTZDict = None) -> datetime: ... - - -def parse_datetime(time_container, available_tz=None): - if time_container is None: - return None - - tz_list = time_container.params.get('TZID') - param_tz: Optional[str] = tz_list[0] if tz_list else None - # if ('T' not in time_container.value) and 'DATE' in time_container.params.get('VALUE', []): - val = time_container.value - fixed_utc = (val[-1].upper() == 'Z') - - val = val.translate({ - ord("/"): "", - ord("-"): "", - ord("Z"): "", - ord("z"): ""}) - dt = datetime.strptime(val, DATE_FORMATS[len(val)]) - - if fixed_utc: - if param_tz: - raise ValueError("can't specify UTC via appended 'Z' and TZID param '%s'" % param_tz) - return dt.replace(tzinfo=dateutil_tzutc) - elif param_tz: - selected_tz = None - if available_tz: - selected_tz = available_tz.get(param_tz, None) - if selected_tz is None: - selected_tz = gettz(param_tz) # be lenient with missing vtimezone definitions - return dt.replace(tzinfo=selected_tz) - else: - return dt - - -@overload -def parse_date(time_container: None, available_tz: OptionalTZDict = None) -> None: ... - - -@overload -def parse_date(time_container: ContentLine, available_tz: OptionalTZDict = None) -> datetime: ... - - -def parse_date(time_container, available_tz=None): - dt = parse_datetime(time_container, available_tz) - if dt: - return ensure_datetime(dt.date()) - else: - return None - - -@overload -def ensure_datetime(value: None) -> None: ... - - -@overload -def ensure_datetime(value: DatetimeLike) -> datetime: ... - - -def ensure_datetime(value): - if value is None: - return None - elif isinstance(value, datetime): - return value - elif isinstance(value, date): - return datetime.combine(value, midnight, tzinfo=None) - elif isinstance(value, tuple): - return datetime(*value) - elif isinstance(value, dict): - return datetime(**value) - else: - raise ValueError("can't construct datetime from %s" % repr(value)) - - -def serialize_datetime(instant: datetime, is_utc: bool = False) -> str: - if is_utc: - return instant.strftime('%Y%m%dT%H%M%SZ') - else: - return instant.strftime('%Y%m%dT%H%M%S') - - -def serialize_datetime_to_contentline(name: str, instant: datetime, used_timezones: OptionalTZDict = None) -> ContentLine: - # ToDo keep track of used_timezones - if instant.tzinfo is not None: - tzname = instant.tzinfo.tzname(instant) - if tzname is None: - raise ValueError("timezone of instant '%s' is not None but has no name" % instant) - if is_utc(instant): - return ContentLine(name, value=serialize_datetime(instant, True)) - if used_timezones: - used_timezones[tzname] = instant.tzinfo - return ContentLine(name, params={'TZID': [tzname]}, value=serialize_datetime(instant, False)) - else: - return ContentLine(name, value=serialize_datetime(instant, False)) - - -def now_in_utc() -> datetime: - return datetime.now(tz=dateutil_tzutc) - - -def is_utc(instant: datetime) -> bool: - tz = instant.tzinfo - if tz is None: - return False - if tz in [dateutil_tzutc, datetime_tzutc]: - return True - offset = tz.utcoffset(instant) - if offset == TIMEDELTA_CACHE[0]: - return True - # tzname = tz.tzname(instant) - # if tzname and tzname.upper() == "UTC": - # return True - return False - - -def serialize_date(instant: DatetimeLike) -> str: - if not isinstance(instant, date): - instant = ensure_datetime(instant).date() - return instant.strftime('%Y%m%d') - - -def iso_precision(string: str) -> str: - has_time = 'T' in string - if has_time: - return 'second' - else: - return 'day' - - -@overload -def ensure_timedelta(value: None) -> None: ... - - -@overload -def ensure_timedelta(value: TimedeltaLike) -> timedelta: ... - - -def ensure_timedelta(value): - if value is None: - return None - elif isinstance(value, timedelta): - return value - elif isinstance(value, tuple): - return timedelta(*value) - elif isinstance(value, dict): - return timedelta(**value) - else: - raise ValueError("can't construct timedelta from %s" % repr(value)) - - -def parse_duration(line: str) -> timedelta: - """ - Return a timedelta object from a string in the DURATION property format - """ - DAYS = {'D': 1, 'W': 7} - SECS = {'S': 1, 'M': 60, 'H': 3600} - - sign, i = 1, 0 - if line[i] in '-+': - if line[i] == '-': - sign = -1 - i += 1 - if line[i] != 'P': - raise ParseError("Error while parsing %s" % line) - i += 1 - days, secs = 0, 0 - while i < len(line): - if line[i] == 'T': - i += 1 - if i == len(line): - break - j = i - while line[j].isdigit(): - j += 1 - if i == j: - raise ParseError("Error while parsing %s" % line) - val = int(line[i:j]) - if line[j] in DAYS: - days += val * DAYS[line[j]] - DAYS.pop(line[j]) - elif line[j] in SECS: - secs += val * SECS[line[j]] - SECS.pop(line[j]) - else: - raise ParseError("Error while parsing %s" % line) - i = j + 1 - return timedelta(sign * days, sign * secs) - - -def serialize_duration(dt: timedelta) -> str: - """ - Return a string according to the DURATION property format - from a timedelta object - """ - ONE_DAY_IN_SECS = 3600 * 24 - total = abs(int(dt.total_seconds())) - days = total // ONE_DAY_IN_SECS - seconds = total % ONE_DAY_IN_SECS - - res = '' - if days: - res += str(days) + 'D' - if seconds: - res += 'T' - if seconds // 3600: - res += str(seconds // 3600) + 'H' - seconds %= 3600 - if seconds // 60: - res += str(seconds // 60) + 'M' - seconds %= 60 - if seconds: - res += str(seconds) + 'S' - - if not res: - res = 'T0S' - if dt.total_seconds() >= 0: - return 'P' + res - else: - return '-P%s' % res - - -############################################################################### -# Rounding Utils - -@overload -def floor_datetime_to_midnight(value: datetime) -> datetime: ... - - -@overload -def floor_datetime_to_midnight(value: date) -> date: ... - - -@overload -def floor_datetime_to_midnight(value: None) -> None: ... - - -def floor_datetime_to_midnight(value): - if value is None: - return None - if isinstance(value, date) and not isinstance(value, datetime): - return value - return datetime.combine(ensure_datetime(value).date(), midnight, tzinfo=value.tzinfo) - - -@overload -def ceil_datetime_to_midnight(value: datetime) -> datetime: ... - - -@overload -def ceil_datetime_to_midnight(value: date) -> date: ... - - -@overload -def ceil_datetime_to_midnight(value: None) -> None: ... - - -def ceil_datetime_to_midnight(value): - if value is None: - return None - if isinstance(value, date) and not isinstance(value, datetime): - return value - floored = floor_datetime_to_midnight(value) - if floored != value: - return floored + TIMEDELTA_CACHE["day"] - else: - return floored - - -def floor_timedelta_to_days(value: timedelta) -> timedelta: - return value - (value % TIMEDELTA_CACHE["day"]) - - -def ceil_timedelta_to_days(value: timedelta) -> timedelta: - mod = value % TIMEDELTA_CACHE["day"] - if mod == TIMEDELTA_CACHE[0]: - return value - else: - return value + TIMEDELTA_CACHE["day"] - mod - - -############################################################################### -# String Utils - - -def get_lines(container: Container, name: str, keep: bool = False) -> ContainerList: - # FIXME this can be done so much faster by using bucketing - lines = [] - for i in reversed(range(len(container))): - item = container[i] - if item.name == name: - lines.append(item) - if not keep: - del container[i] - return lines - - -def remove_x(container: Container) -> None: - for i in reversed(range(len(container))): - item = container[i] - if item.name.startswith('X-'): - del container[i] - - -def remove_sequence(container: Container) -> None: - for i in reversed(range(len(container))): - item = container[i] - if item.name == 'SEQUENCE': - del container[i] - - -def uid_gen() -> str: - uid = str(uuid4()) - return "{}@{}.org".format(uid, uid[:4]) - - -def escape_string(string: str) -> str: - return string.translate( - {ord("\\"): "\\\\", - ord(";"): "\\;", - ord(","): "\\,", - ord("\n"): "\\n", - ord("\r"): "\\r"}) - - -def unescape_string(string: str) -> str: - return "".join(unescape_string_iter(string)) - - -def unescape_string_iter(string: str) -> Generator[str, None, None]: - it = iter(string) - for c1 in it: - if c1 == "\\": - c2 = next(it) - if c2 == ";": - yield ";" - elif c2 == ",": - yield "," - elif c2 == "n" or c2 == "N": - yield "\n" - elif c2 == "r" or c2 == "R": - yield "\r" - elif c2 == "\\": - yield "\\" - else: - raise ValueError("can't handle escaped character '%s'" % c2) - else: - yield c1 - - -############################################################################### - -def validate_not_none(inst, attr, value): - if value is None: - raise ValueError( - "'{name}' may not be None".format( - name=attr.name - ) - ) - - -def validate_truthy(inst, attr, value): - if not bool(value): - raise ValueError( - "'{name}' must be truthy (got {value!r})".format( - name=attr.name, value=value - ) - ) - - -def check_is_instance(name, value, clazz): - if not isinstance(value, clazz): - raise TypeError( - "'{name}' must be {type!r} (got {value!r} that is a " - "{actual!r}).".format( - name=name, - type=clazz, - actual=value.__class__, - value=value, - ), - name, - clazz, - value, - ) - - -def validate_utc(inst, attr, value): - check_is_instance(attr.name, value, datetime) - if not is_utc(value): - raise ValueError( - "'{name}' must be in timezone UTC (got {value!r} which has tzinfo {tzinfo!r})".format( - name=attr.name, value=value, tzinfo=value.tzinfo - ) - ) diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 281dd2ec..00000000 --- a/mypy.ini +++ /dev/null @@ -1,3 +0,0 @@ -[mypy] -ignore_missing_imports = True -check_untyped_defs = True diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..4cb42efd --- /dev/null +++ b/poetry.lock @@ -0,0 +1,491 @@ +[[package]] +category = "main" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" +optional = true +python-versions = "*" +version = "1.4.4" + +[[package]] +category = "main" +description = "Atomic file writes." +marker = "sys_platform == \"win32\"" +name = "atomicwrites" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.4.0" + +[[package]] +category = "main" +description = "Classes Without Boilerplate" +name = "attrs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.3.0" + +[package.extras] +azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] +dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] +docs = ["sphinx", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] + +[[package]] +category = "main" +description = "Version-bump your software with a single command!" +name = "bump2version" +optional = true +python-versions = ">=3.5" +version = "1.0.0" + +[[package]] +category = "main" +description = "Cross-platform colored terminal text." +marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" +name = "colorama" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.3" + +[[package]] +category = "main" +description = "Code coverage measurement for Python" +name = "coverage" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "5.1" + +[package.extras] +toml = ["toml"] + +[[package]] +category = "main" +description = "Distribution utilities" +name = "distlib" +optional = true +python-versions = "*" +version = "0.3.0" + +[[package]] +category = "main" +description = "A platform independent file lock." +name = "filelock" +optional = true +python-versions = "*" +version = "3.0.12" + +[[package]] +category = "main" +description = "A library for property-based testing" +name = "hypothesis" +optional = true +python-versions = ">=3.5.2" +version = "5.14.0" + +[package.dependencies] +attrs = ">=19.2.0" +sortedcontainers = ">=2.1.0,<3.0.0" + +[package.extras] +all = ["django (>=2.2)", "dpcontracts (>=0.4)", "lark-parser (>=0.6.5)", "numpy (>=1.9.0)", "pandas (>=0.19)", "pytest (>=4.3)", "python-dateutil (>=1.4)", "pytz (>=2014.1)"] +dateutil = ["python-dateutil (>=1.4)"] +django = ["pytz (>=2014.1)", "django (>=2.2)"] +dpcontracts = ["dpcontracts (>=0.4)"] +lark = ["lark-parser (>=0.6.5)"] +numpy = ["numpy (>=1.9.0)"] +pandas = ["pandas (>=0.19)"] +pytest = ["pytest (>=4.3)"] +pytz = ["pytz (>=2014.1)"] + +[[package]] +category = "main" +description = "Read metadata from Python packages" +marker = "python_version < \"3.8\"" +name = "importlib-metadata" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "1.6.0" + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "rst.linker"] +testing = ["packaging", "importlib-resources"] + +[[package]] +category = "main" +description = "Read resources from Python packages" +name = "importlib-resources" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "1.5.0" + +[package.dependencies] +[package.dependencies.importlib-metadata] +python = "<3.8" +version = "*" + +[package.dependencies.zipp] +python = "<3.8" +version = ">=0.4" + +[package.extras] +docs = ["sphinx", "rst.linker", "jaraco.packaging"] + +[[package]] +category = "main" +description = "More routines for operating on iterables, beyond itertools" +name = "more-itertools" +optional = true +python-versions = ">=3.5" +version = "8.3.0" + +[[package]] +category = "main" +description = "Core utilities for Python packages" +name = "packaging" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.3" + +[package.dependencies] +pyparsing = ">=2.0.2" +six = "*" + +[[package]] +category = "main" +description = "plugin and hook calling mechanisms for python" +name = "pluggy" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.13.1" + +[package.dependencies] +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +category = "main" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.8.1" + +[[package]] +category = "main" +description = "Python parsing module" +name = "pyparsing" +optional = true +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "2.4.7" + +[[package]] +category = "main" +description = "pytest: simple powerful testing with Python" +name = "pytest" +optional = true +python-versions = ">=3.5" +version = "5.4.2" + +[package.dependencies] +atomicwrites = ">=1.0" +attrs = ">=17.4.0" +colorama = "*" +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" + +[package.extras] +checkqa-mypy = ["mypy (v0.761)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +category = "main" +description = "Pytest plugin for measuring coverage." +name = "pytest-cov" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.8.1" + +[package.dependencies] +coverage = ">=4.4" +pytest = ">=3.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "virtualenv"] + +[[package]] +category = "main" +description = "Extensions to the standard Python datetime module" +name = "python-dateutil" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +version = "2.8.1" + +[package.dependencies] +six = ">=1.5" + +[[package]] +category = "main" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.14.0" + +[[package]] +category = "main" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +name = "sortedcontainers" +optional = true +python-versions = "*" +version = "2.1.0" + +[[package]] +category = "main" +description = "TatSu takes a grammar in a variation of EBNF as input, and outputs a memoizing PEG/Packrat parser in Python." +name = "tatsu" +optional = false +python-versions = "*" +version = "4.4.0" + +[package.extras] +future-regex = ["regex"] + +[[package]] +category = "main" +description = "Python Library for Tom's Obvious, Minimal Language" +name = "toml" +optional = true +python-versions = "*" +version = "0.10.1" + +[[package]] +category = "main" +description = "tox is a generic virtualenv management and test command line tool" +name = "tox" +optional = true +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "3.15.0" + +[package.dependencies] +colorama = ">=0.4.1" +filelock = ">=3.0.0,<4" +packaging = ">=14" +pluggy = ">=0.12.0,<1" +py = ">=1.4.17,<2" +six = ">=1.14.0,<2" +toml = ">=0.9.4" +virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12,<2" + +[package.extras] +docs = ["sphinx (>=2.0.0,<3)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"] +testing = ["freezegun (>=0.3.11,<1)", "pathlib2 (>=2.3.3,<3)", "pytest (>=4.0.0,<6)", "pytest-cov (>=2.5.1,<3)", "pytest-mock (>=1.10.0,<2)", "pytest-xdist (>=1.22.2,<2)", "pytest-randomly (>=1.0.0,<4)", "flaky (>=3.4.0,<4)", "psutil (>=5.6.1,<6)"] + +[[package]] +category = "main" +description = "Virtual Python Environment builder" +name = "virtualenv" +optional = true +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "20.0.20" + +[package.dependencies] +appdirs = ">=1.4.3,<2" +distlib = ">=0.3.0,<1" +filelock = ">=3.0.0,<4" +six = ">=1.9.0,<2" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12,<2" + +[package.dependencies.importlib-resources] +python = "<3.7" +version = ">=1.0,<2" + +[package.extras] +docs = ["sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2)"] +testing = ["pytest (>=4)", "coverage (>=5)", "coverage-enable-subprocess (>=1)", "pytest-xdist (>=1.31.0)", "pytest-mock (>=2)", "pytest-env (>=0.6.2)", "pytest-randomly (>=1)", "pytest-timeout", "packaging (>=20.0)", "xonsh (>=0.9.16)"] + +[[package]] +category = "main" +description = "Measures number of Terminal column cells of wide-character codes" +name = "wcwidth" +optional = true +python-versions = "*" +version = "0.1.9" + +[[package]] +category = "main" +description = "Backport of pathlib-compatible object wrapper for zip files" +marker = "python_version < \"3.8\"" +name = "zipp" +optional = false +python-versions = ">=3.6" +version = "3.1.0" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["jaraco.itertools", "func-timeout"] + +[extras] +dev = ["bump2version", "tox"] +test = ["pytest", "pytest-cov", "hypothesis"] + +[metadata] +content-hash = "f97e1d927a9d63080cfb692d6ec34a495e5b16d939663d37641df0784de2eb5b" +python-versions = "^3.6" + +[metadata.files] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, + {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, +] +bump2version = [ + {file = "bump2version-1.0.0-py2.py3-none-any.whl", hash = "sha256:477f0e18a0d58e50bb3dbc9af7fcda464fd0ebfc7a6151d8888602d7153171a0"}, + {file = "bump2version-1.0.0.tar.gz", hash = "sha256:cd4f3a231305e405ed8944d8ff35bd742d9bc740ad62f483bd0ca21ce7131984"}, +] +colorama = [ + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, +] +coverage = [ + {file = "coverage-5.1-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65"}, + {file = "coverage-5.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2"}, + {file = "coverage-5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04"}, + {file = "coverage-5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6"}, + {file = "coverage-5.1-cp27-cp27m-win32.whl", hash = "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796"}, + {file = "coverage-5.1-cp27-cp27m-win_amd64.whl", hash = "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730"}, + {file = "coverage-5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0"}, + {file = "coverage-5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a"}, + {file = "coverage-5.1-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf"}, + {file = "coverage-5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9"}, + {file = "coverage-5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768"}, + {file = "coverage-5.1-cp35-cp35m-win32.whl", hash = "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2"}, + {file = "coverage-5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7"}, + {file = "coverage-5.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0"}, + {file = "coverage-5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019"}, + {file = "coverage-5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c"}, + {file = "coverage-5.1-cp36-cp36m-win32.whl", hash = "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1"}, + {file = "coverage-5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7"}, + {file = "coverage-5.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355"}, + {file = "coverage-5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489"}, + {file = "coverage-5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd"}, + {file = "coverage-5.1-cp37-cp37m-win32.whl", hash = "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e"}, + {file = "coverage-5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a"}, + {file = "coverage-5.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55"}, + {file = "coverage-5.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c"}, + {file = "coverage-5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef"}, + {file = "coverage-5.1-cp38-cp38-win32.whl", hash = "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24"}, + {file = "coverage-5.1-cp38-cp38-win_amd64.whl", hash = "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0"}, + {file = "coverage-5.1-cp39-cp39-win32.whl", hash = "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4"}, + {file = "coverage-5.1-cp39-cp39-win_amd64.whl", hash = "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e"}, + {file = "coverage-5.1.tar.gz", hash = "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"}, +] +distlib = [ + {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, +] +filelock = [ + {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, + {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +] +hypothesis = [ + {file = "hypothesis-5.14.0-py3-none-any.whl", hash = "sha256:ca58d0d6b37b10c9ae5cfaa97fb9cb8b033f0cbacb19dfea3fed79a7d57cfcfd"}, + {file = "hypothesis-5.14.0.tar.gz", hash = "sha256:0c00645c63341f035951cade156112d7c4d3c650c5a54671d104dc8acd41a350"}, +] +importlib-metadata = [ + {file = "importlib_metadata-1.6.0-py2.py3-none-any.whl", hash = "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f"}, + {file = "importlib_metadata-1.6.0.tar.gz", hash = "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"}, +] +importlib-resources = [ + {file = "importlib_resources-1.5.0-py2.py3-none-any.whl", hash = "sha256:85dc0b9b325ff78c8bef2e4ff42616094e16b98ebd5e3b50fe7e2f0bbcdcde49"}, + {file = "importlib_resources-1.5.0.tar.gz", hash = "sha256:6f87df66833e1942667108628ec48900e02a4ab4ad850e25fbf07cb17cf734ca"}, +] +more-itertools = [ + {file = "more-itertools-8.3.0.tar.gz", hash = "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be"}, + {file = "more_itertools-8.3.0-py3-none-any.whl", hash = "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"}, +] +packaging = [ + {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, + {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +py = [ + {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, + {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-5.4.2-py3-none-any.whl", hash = "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3"}, + {file = "pytest-5.4.2.tar.gz", hash = "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698"}, +] +pytest-cov = [ + {file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"}, + {file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, + {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, +] +six = [ + {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, + {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, +] +sortedcontainers = [ + {file = "sortedcontainers-2.1.0-py2.py3-none-any.whl", hash = "sha256:d9e96492dd51fae31e60837736b38fe42a187b5404c16606ff7ee7cd582d4c60"}, + {file = "sortedcontainers-2.1.0.tar.gz", hash = "sha256:974e9a32f56b17c1bac2aebd9dcf197f3eb9cd30553c5852a3187ad162e1a03a"}, +] +tatsu = [ + {file = "TatSu-4.4.0-py2.py3-none-any.whl", hash = "sha256:c9211eeee9a2d4c90f69879ec0b518b1aa0d9450249cb0dd181f5f5b18be0a92"}, + {file = "TatSu-4.4.0.zip", hash = "sha256:80713413473a009f2081148d0f494884cabaf9d6866b71f2a68a92b6442f343d"}, +] +toml = [ + {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, + {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, +] +tox = [ + {file = "tox-3.15.0-py2.py3-none-any.whl", hash = "sha256:8d97bfaf70053ed3db56f57377288621f1bcc7621446d301927d18df93b1c4c3"}, + {file = "tox-3.15.0.tar.gz", hash = "sha256:af09c19478e8fc7ce7555b3d802ddf601b82684b874812c5857f774b8aee1b67"}, +] +virtualenv = [ + {file = "virtualenv-20.0.20-py2.py3-none-any.whl", hash = "sha256:b4c14d4d73a0c23db267095383c4276ef60e161f94fde0427f2f21a0132dde74"}, + {file = "virtualenv-20.0.20.tar.gz", hash = "sha256:fd0e54dec8ac96c1c7c87daba85f0a59a7c37fe38748e154306ca21c73244637"}, +] +wcwidth = [ + {file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"}, + {file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"}, +] +zipp = [ + {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, + {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..74e3e072 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +[tool.poetry] +name = "ics" +version = "0.8.0-dev" +description = "Pythonic iCalendar (RFC 5545) Parser" +authors = ["Nikita Marchant ", "Niko Fink "] +license = "Apache-2.0" +readme = "README.rst" +homepage = "https://pypi.org/project/ics/" +repository = "https://github.com/C4ptainCrunch/ics.py" +documentation = "https://icspy.readthedocs.io/en/stable/" +keywords = ["ics", "icalendar", "calendar", "event", "rfc5545"] +classifiers = [ + 'Development Status :: 4 - Beta', + 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Intended Audience :: Developers', + 'Topic :: Office/Business :: Scheduling', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Typing :: Typed', +] + +[tool.poetry.dependencies] +python = "^3.6" +python-dateutil = "^2.8" +attrs = ">=19.2" +tatsu = ">4.2" +importlib_resources = "^1.4" + +# extra: test +pytest = { version = "^5.2", optional = true } +pytest-cov = { version = "^2.8.1", optional = true } +hypothesis = { version = "^5.8.0", optional = true } + +# extra: dev +bump2version = { version = "^1.0.0", optional = true } +tox = { version = "^3.15.0", optional = true } + +[tool.poetry.extras] +test = ["pytest", "pytest-cov", "hypothesis"] +dev = ["bump2version", "tox"] + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 558d0904..00000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -python-dateutil -six>1.5 -tatsu>4.2 -attrs>=19.2 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 657c4d9f..00000000 --- a/setup.cfg +++ /dev/null @@ -1,47 +0,0 @@ -[tool:pytest] -python_files = *.py -flakes-ignore = - UnusedImport - UndefinedName -pep8ignore = - tests/*.py ALL - doc/_themes/flask_theme_support.py ALL - E501 - E128 - F403 - F401 - E261 - E265 - W503 - E701 - E251 - E127 - E731 - E704 - -norecursedirs = ve .git .eggs .cache ics.egg-info doc -testpaths = ics tests -addopts = --flakes --pep8 - - - -[flake8] -ignore = E128,E261,E265,E501,F403,F401,W503 -exclude = doc/,ve/,ve3/ - -[metadata] -license-file = LICENSE.rst - -[check-manifest] -ignore = - .travis.yml - doc/* - doc - requirements*.txt - test - dev/* - dev - .coveragerc - -[wheel] -universal = 1 diff --git a/setup.py b/setup.py deleted file mode 100755 index 75eea85c..00000000 --- a/setup.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python -import sys - -from setuptools import setup -from setuptools.command.test import test as TestCommand - -from ics.__meta__ import __author__, __license__, __title__, __version__ - -with open("requirements.txt") as f: - install_requires = [line for line in f if line and line[0] not in "#-"] - -with open("dev/requirements-test.txt") as f: - tests_require = [line for line in f if line and line[0] not in "#-"] - - -class PyTest(TestCommand): - - def finalize_options(self): - TestCommand.finalize_options(self) - self.test_args = [ - '--cov', - 'ics', - 'ics/', - 'tests/' - ] - self.test_suite = True - - def run_tests(self): - # import here, cause outside the eggs aren't loaded - import pytest - errno = pytest.main(self.test_args) - sys.exit(errno) - - -def readme(): - with open('README.rst', encoding='utf-8') as f: - return f.read() - - -setup( - name=__title__, - version=__version__, - description='Python icalendar (rfc5545) parser', - long_description=readme(), - keywords='ics icalendar calendar event todo rfc5545 parser pythonic', - classifiers=[ - 'Development Status :: 4 - Beta', - 'Topic :: Software Development :: Libraries', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Intended Audience :: Developers', - 'Topic :: Office/Business :: Scheduling', - 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Typing :: Typed', - - ], - url='http://github.com/C4ptainCrunch/ics.py', - author=__author__, - author_email='nikita.marchant@gmail.com', - install_requires=install_requires, - license=__license__, - packages=['ics'], - include_package_data=True, - cmdclass={'test': PyTest}, - tests_require=tests_require, - test_suite="py.test", - zip_safe=False, -) diff --git a/src/ics/__init__.py b/src/ics/__init__.py new file mode 100644 index 00000000..ea024f6c --- /dev/null +++ b/src/ics/__init__.py @@ -0,0 +1,41 @@ +def load_converters(): + from ics.converter.base import AttributeConverter + from ics.converter.component import ComponentConverter + from ics.converter.special import TimezoneConverter, AlarmConverter, PersonConverter, RecurrenceConverter + from ics.converter.timespan import TimespanConverter + from ics.converter.value import AttributeValueConverter + from ics.valuetype.base import ValueConverter + from ics.valuetype.datetime import DateConverter, DatetimeConverter, DurationConverter, PeriodConverter, TimeConverter, UTCOffsetConverter + from ics.valuetype.generic import BinaryConverter, BooleanConverter, CalendarUserAddressConverter, FloatConverter, IntegerConverter, RecurConverter, URIConverter + from ics.valuetype.text import TextConverter + from ics.valuetype.special import GeoConverter + + +load_converters() # make sure that converters are initialized before any Component classes are defined + +from .alarm import * +from .alarm import __all__ as all_alarms +from .attendee import Attendee, Organizer +from .component import Component +from .event import Event +from .geo import Geo +from .grammar import Container, ContentLine +from .icalendar import Calendar +from .timespan import EventTimespan, Timespan, TodoTimespan +from .todo import Todo + +__all__ = [ + *all_alarms, + "Attendee", + "Event", + "Calendar", + "Organizer", + "Timespan", + "EventTimespan", + "TodoTimespan", + "Todo", + "Component", + "__version__" +] + +__version__ = "0.8.0-dev" diff --git a/src/ics/alarm.py b/src/ics/alarm.py new file mode 100644 index 00000000..5bc3ca08 --- /dev/null +++ b/src/ics/alarm.py @@ -0,0 +1,135 @@ +from abc import ABCMeta, abstractmethod +from datetime import datetime, timedelta +from typing import List, Union + +import attr +from attr.converters import optional as c_optional +from attr.validators import instance_of, optional as v_optional + +from ics.attendee import Attendee +from ics.component import Component +from ics.converter.component import ComponentMeta +from ics.converter.special import AlarmConverter +from ics.types import URL +from ics.utils import call_validate_on_inst, check_is_instance, ensure_timedelta + +__all__ = ["BaseAlarm", "AudioAlarm", "CustomAlarm", "DisplayAlarm", "EmailAlarm", "NoneAlarm"] + + +@attr.s +class BaseAlarm(Component, metaclass=ABCMeta): + """ + A calendar event VALARM base class + """ + Meta = ComponentMeta("VALARM", converter_class=AlarmConverter) + + trigger: Union[timedelta, datetime, None] = attr.ib( + default=None, + validator=v_optional(instance_of((timedelta, datetime))) + ) # TODO is this relative to begin or end? + repeat: int = attr.ib(default=None, validator=call_validate_on_inst) + duration: timedelta = attr.ib(default=None, converter=c_optional(ensure_timedelta), validator=call_validate_on_inst) # type: ignore + + # FIXME: `attach` can be specified multiple times in a "VEVENT", "VTODO", "VJOURNAL", or "VALARM" calendar component + # with the exception of AUDIO alarm that only allows this property to occur once. + # (This property is used in "VALARM" calendar components to specify an audio sound resource or an email message attachment.) + + def validate(self, attr=None, value=None): + if self.repeat is not None: + if self.repeat < 0: + raise ValueError("Repeat must be great than or equal to 0.") + if self.duration is None: + raise ValueError( + "A definition of an alarm with a repeating trigger MUST include both the DURATION and REPEAT properties." + ) + + if self.duration is not None and self.duration.total_seconds() < 0: + raise ValueError("Alarm duration timespan must be positive.") + + @property + @abstractmethod + def action(self): + """ VALARM action to be implemented by concrete classes """ + ... + + +@attr.s +class AudioAlarm(BaseAlarm): + """ + A calendar event VALARM with AUDIO option. + """ + + attach: Union[URL, bytes, None] = attr.ib(default=None) + + @property + def action(self): + return "AUDIO" + + +@attr.s +class CustomAlarm(BaseAlarm): + """ + A calendar event VALARM with custom ACTION. + """ + + _action = attr.ib(default=None) + + @property + def action(self): + return self._action + + +@attr.s +class DisplayAlarm(BaseAlarm): + """ + A calendar event VALARM with DISPLAY option. + """ + + display_text: str = attr.ib(default=None) + + @property + def action(self): + return "DISPLAY" + + +@attr.s +class EmailAlarm(BaseAlarm): + """ + A calendar event VALARM with Email option. + """ + + subject: str = attr.ib(default=None) + body: str = attr.ib(default=None) + recipients: List[Attendee] = attr.ib(factory=list) + + def add_recipient(self, recipient: Attendee): + """ Add an recipient to the recipients list """ + check_is_instance("recipient", recipient, Attendee) + self.recipients.append(recipient) + + @property + def action(self): + return "EMAIL" + + +class NoneAlarm(BaseAlarm): + """ + A calendar event VALARM with NONE option. + """ + + @property + def action(self): + return "NONE" + + +def get_type_from_action(action_type): + if action_type == "DISPLAY": + return DisplayAlarm + elif action_type == "AUDIO": + return AudioAlarm + elif action_type == "NONE": + return NoneAlarm + elif action_type == "EMAIL": + return EmailAlarm + else: + return CustomAlarm diff --git a/src/ics/attendee.py b/src/ics/attendee.py new file mode 100644 index 00000000..330bc50c --- /dev/null +++ b/src/ics/attendee.py @@ -0,0 +1,30 @@ +from typing import Dict, List, Optional + +import attr + +from ics.converter.component import ComponentMeta + + +@attr.s +class Person(object): + email: str = attr.ib() + common_name: str = attr.ib(default=None) + dir: Optional[str] = attr.ib(default=None) + sent_by: Optional[str] = attr.ib(default=None) + extra: Dict[str, List[str]] = attr.ib(factory=dict) + + Meta = ComponentMeta("ABSTRACT-PERSON") + + +class Organizer(Person): + Meta = ComponentMeta("ORGANIZER") + + +@attr.s +class Attendee(Person): + rsvp: Optional[bool] = attr.ib(default=None) + role: Optional[str] = attr.ib(default=None) + partstat: Optional[str] = attr.ib(default=None) + cutype: Optional[str] = attr.ib(default=None) + + Meta = ComponentMeta("ATTENDEE") diff --git a/src/ics/component.py b/src/ics/component.py new file mode 100644 index 00000000..f21c6269 --- /dev/null +++ b/src/ics/component.py @@ -0,0 +1,66 @@ +from typing import ClassVar, Dict, List, Type, TypeVar, Union + +import attr +from attr.validators import instance_of + +from ics.converter.component import ComponentMeta +from ics.grammar import Container +from ics.types import ExtraParams, RuntimeAttrValidation + +PLACEHOLDER_CONTAINER = Container("PLACEHOLDER") +ComponentType = TypeVar('ComponentType', bound='Component') +ComponentExtraParams = Dict[str, Union[ExtraParams, List[ExtraParams]]] + + +@attr.s +class Component(RuntimeAttrValidation): + Meta: ClassVar[ComponentMeta] = ComponentMeta("ABSTRACT-COMPONENT") + + extra: Container = attr.ib(init=False, default=PLACEHOLDER_CONTAINER, validator=instance_of(Container), metadata={"ics_ignore": True}) + extra_params: ComponentExtraParams = attr.ib(init=False, factory=dict, validator=instance_of(dict), metadata={"ics_ignore": True}) + + def __attrs_post_init__(self): + super(Component, self).__attrs_post_init__() + if self.extra is PLACEHOLDER_CONTAINER: + self.extra = Container(self.Meta.container_name) + + def __init_subclass__(cls): + super().__init_subclass__() + cls.Meta.inflate(cls) + + @classmethod + def from_container(cls: Type[ComponentType], container: Container) -> ComponentType: + return cls.Meta.load_instance(container) + + def populate(self, container: Container): + self.Meta.populate_instance(self, container) + + def to_container(self) -> Container: + return self.Meta.serialize_toplevel(self) + + def serialize(self) -> str: + return self.to_container().serialize() + + def strip_extras(self, all_extras=False, extra_properties=None, extra_params=None, property_merging=None): + if extra_properties is None: + extra_properties = all_extras + if extra_params is None: + extra_params = all_extras + if property_merging is None: + property_merging = all_extras + if not any([extra_properties, extra_params, property_merging]): + raise ValueError("need to strip at least one thing") + if extra_properties: + self.extra.clear() + if extra_params: + self.extra_params.clear() + elif property_merging: + for val in self.extra_params.values(): + if not isinstance(val, list): continue + for v in val: + v.pop("__merge_next", None) + + def clone(self): + """Returns an exact (shallow) copy of self""" + # TODO deep copies? + return attr.evolve(self) diff --git a/ics/grammar/__init__.py b/src/ics/converter/__init__.py similarity index 100% rename from ics/grammar/__init__.py rename to src/ics/converter/__init__.py diff --git a/src/ics/converter/base.py b/src/ics/converter/base.py new file mode 100644 index 00000000..ac2728af --- /dev/null +++ b/src/ics/converter/base.py @@ -0,0 +1,207 @@ +import abc +import warnings +from types import SimpleNamespace +from typing import Any, ClassVar, Dict, List, MutableSequence, Optional, TYPE_CHECKING, Tuple, Type, Union, cast + +import attr + +from ics.grammar import Container +from ics.types import ContainerItem, ContextDict, ExtraParams + +if TYPE_CHECKING: + from ics.component import Component + from ics.converter.component import InflatedComponentMeta + +NoneTypes = [type(None), None] + + +# TODO make validation / ValueError / warnings configurable +# TODO use repr for warning messages and ensure that they don't get to long + +class GenericConverter(abc.ABC): + @property + @abc.abstractmethod + def priority(self) -> int: + ... + + @property + @abc.abstractmethod + def filter_ics_names(self) -> List[str]: + ... + + @abc.abstractmethod + def populate(self, component: "Component", item: ContainerItem, context: ContextDict) -> bool: + """ + :param context: + :param component: + :param item: + :return: True, if the line was consumed and shouldn't be stored as extra (but might still be passed on) + """ + ... + + def finalize(self, component: "Component", context: ContextDict): + ... + + @abc.abstractmethod + def serialize(self, component: "Component", output: Container, context: ContextDict): + ... + + +@attr.s(frozen=True) +class AttributeConverter(GenericConverter, abc.ABC): + BY_TYPE: ClassVar[Dict[Type, Type["AttributeConverter"]]] = {} + + attribute: attr.Attribute = attr.ib() + + multi_value_type: Optional[Type[MutableSequence]] + value_type: Type + value_types: List[Type] + _priority: int + is_required: bool + + def __attrs_post_init__(self): + v = SimpleNamespace() + v.multi_value_type, v.value_type, v.value_types = extract_attr_type(self.attribute) + v._priority = self.attribute.metadata.get("ics_priority", self.default_priority) + v.is_required = self.attribute.metadata.get("ics_required", None) + if v.is_required is None: + if not self.attribute.init: + v.is_required = False + elif self.attribute.default is not attr.NOTHING: + v.is_required = False + else: + v.is_required = True + for key, value in v.__dict__.items(): # all variables created in __attrs_post_init__.v will be set on self + object.__setattr__(self, key, value) + + def _check_component(self, component: "Component", context: ContextDict): + if context[(self, "current_component")] is None: + context[(self, "current_component")] = component + context[(self, "current_value_count")] = 0 + else: + if context[(self, "current_component")] is not component: + raise ValueError("must call finalize before call to populate with another component") + + def finalize(self, component: "Component", context: ContextDict): + context[(self, "current_component")] = None + context[(self, "current_value_count")] = 0 + + def set_or_append_value(self, component: "Component", value: Any): + if self.multi_value_type is not None: + container = getattr(component, self.attribute.name) + if container is None: + container = self.multi_value_type() + setattr(component, self.attribute.name, container) + container.append(value) + else: + setattr(component, self.attribute.name, value) + + def get_value(self, component: "Component") -> Any: + return getattr(component, self.attribute.name) + + def get_value_list(self, component: "Component") -> List[Any]: + if self.is_multi_value: + return list(self.get_value(component)) + else: + return [self.get_value(component)] + + def set_or_append_extra_params(self, component: "Component", value: ExtraParams, name: Optional[str] = None): + name = name or self.attribute.name + if self.is_multi_value: + extras = component.extra_params.setdefault(name, []) + cast(List[ExtraParams], extras).append(value) + elif value: + component.extra_params[name] = value + + def get_extra_params(self, component: "Component", name: Optional[str] = None) -> Union[ExtraParams, List[ExtraParams]]: + if self.multi_value_type: + default: Union[ExtraParams, List[ExtraParams]] = cast(List[ExtraParams], list()) + else: + default = ExtraParams(dict()) + name = name or self.attribute.name + return component.extra_params.get(name, default) + + @property + def default_priority(self) -> int: + return 0 + + @property + def priority(self) -> int: + return self._priority + + @property + def is_multi_value(self) -> bool: + return self.multi_value_type is not None + + @staticmethod + def get_converter_for(attribute: attr.Attribute) -> Optional["AttributeConverter"]: + if attribute.metadata.get("ics_ignore", not attribute.init): + return None + converter = attribute.metadata.get("ics_converter", None) + if converter: + return converter(attribute) + + multi_value_type, value_type, value_types = extract_attr_type(attribute) + if len(value_types) == 1: + assert [value_type] == value_types + from ics.component import Component + if issubclass(value_type, Component): + meta: "InflatedComponentMeta" = cast("InflatedComponentMeta", value_type.Meta) + return meta(attribute) + elif value_type in AttributeConverter.BY_TYPE: + return AttributeConverter.BY_TYPE[value_type](attribute) + + from ics.converter.value import AttributeValueConverter + return AttributeValueConverter(attribute) + + +def extract_attr_type(attribute: attr.Attribute) -> Tuple[Optional[Type[MutableSequence]], Type, List[Type]]: + attr_type = attribute.metadata.get("ics_type", attribute.type) + if attr_type is None: + raise ValueError("can't convert attribute %s with AttributeConverter, " + "as it has no type information" % attribute) + return unwrap_type(attr_type) + + +def unwrap_type(attr_type: Type) -> Tuple[Optional[Type[MutableSequence]], Type, List[Type]]: + generic_origin = getattr(attr_type, "__origin__", attr_type) + generic_vars = getattr(attr_type, "__args__", tuple()) + + if generic_origin == Union: + generic_vars = [v for v in generic_vars if v not in NoneTypes] + if len(generic_vars) > 1: + return None, generic_origin[tuple(generic_vars)], list(generic_vars) + else: + return None, generic_vars[0], [generic_vars[0]] + + elif issubclass(generic_origin, MutableSequence): + if len(generic_vars) > 1: + warnings.warn("using first parameter for List type %s" % attr_type) + res = unwrap_type(generic_vars[0]) + assert res[0] is None + return generic_origin, res[1], res[2] + + else: + return None, attr_type, [attr_type] + + +def ics_attr_meta(name: str = None, + ignore: bool = None, + type: Type = None, + required: bool = None, + priority: int = None, + converter: Type[AttributeConverter] = None) -> Dict[str, Any]: + data: Dict[str, Any] = {} + if name: + data["ics_name"] = name + if ignore is not None: + data["ics_ignore"] = ignore + if type is not None: + data["ics_type"] = type + if required is not None: + data["ics_required"] = required + if priority is not None: + data["ics_priority"] = priority + if converter is not None: + data["ics_converter"] = converter + return data diff --git a/src/ics/converter/component.py b/src/ics/converter/component.py new file mode 100644 index 00000000..b37f5dd9 --- /dev/null +++ b/src/ics/converter/component.py @@ -0,0 +1,111 @@ +from collections import defaultdict +from typing import Dict, Iterable, List, Optional, TYPE_CHECKING, Tuple, Type, cast + +import attr +from attr import Attribute + +from ics.converter.base import AttributeConverter, GenericConverter +from ics.grammar import Container +from ics.types import ContainerItem, ContextDict + +if TYPE_CHECKING: + from ics.component import Component + + +@attr.s(frozen=True) +class ComponentMeta(object): + container_name: str = attr.ib() + converter_class: Type["ComponentConverter"] = attr.ib(default=None) + + def inflate(self, component_type: Type["Component"]): + if component_type.Meta is not self: + raise ValueError("can't inflate %s for %s, it's meta is %s" % (self, component_type, component_type.Meta)) + converters = cast(Iterable["AttributeConverter"], filter(bool, ( + AttributeConverter.get_converter_for(a) + for a in attr.fields(component_type) + ))) + component_type.Meta = InflatedComponentMeta( + component_type=component_type, + converters=tuple(sorted(converters, key=lambda c: c.priority)), + container_name=self.container_name, + converter_class=self.converter_class or ComponentConverter) + + def load_instance(self, container: Container, context: Optional[ContextDict] = None): + raise NotImplementedError("this is only available for InflatedComponentMeta, was Component.__init_subclass__ called?") + + def populate_instance(self, instance: "Component", container: Container, context: Optional[ContextDict] = None): + raise NotImplementedError("this is only available for InflatedComponentMeta, was Component.__init_subclass__ called?") + + def serialize_toplevel(self, component: "Component", context: Optional[ContextDict] = None): + raise NotImplementedError("this is only available for InflatedComponentMeta, was Component.__init_subclass__ called?") + + +@attr.s(frozen=True) +class InflatedComponentMeta(ComponentMeta): + converters: Tuple[GenericConverter, ...] = attr.ib(default=None) + component_type: Type["Component"] = attr.ib(default=None) + + converter_lookup: Dict[str, List[GenericConverter]] + + def __attrs_post_init__(self): + object.__setattr__(self, "converter_lookup", defaultdict(list)) + for converter in self.converters: + for name in converter.filter_ics_names: + self.converter_lookup[name].append(converter) + + def __call__(self, attribute: Attribute): + return self.converter_class(attribute, self) + + def load_instance(self, container: Container, context: Optional[ContextDict] = None): + instance = self.component_type() + self.populate_instance(instance, container, context) + return instance + + def populate_instance(self, instance: "Component", container: Container, context: Optional[ContextDict] = None): + if container.name != self.container_name: + raise ValueError("container isn't an {}".format(self.container_name)) + if not context: + context = ContextDict(defaultdict(lambda: None)) + + for line in container: + consumed = False + for conv in self.converter_lookup[line.name]: + if conv.populate(instance, line, context): + consumed = True + if not consumed: + instance.extra.append(line) + + for conv in self.converters: + conv.finalize(instance, context) + + def serialize_toplevel(self, component: "Component", context: Optional[ContextDict] = None): + if not context: + context = ContextDict(defaultdict(lambda: None)) + container = Container(self.container_name) + for conv in self.converters: + conv.serialize(component, container, context) + container.extend(component.extra) + return container + + +@attr.s(frozen=True) +class ComponentConverter(AttributeConverter): + meta: InflatedComponentMeta = attr.ib() + + @property + def filter_ics_names(self) -> List[str]: + return [self.meta.container_name] + + def populate(self, component: "Component", item: ContainerItem, context: ContextDict) -> bool: + assert isinstance(item, Container) + self._check_component(component, context) + self.set_or_append_value(component, self.meta.load_instance(item, context)) + return True + + def serialize(self, parent: "Component", output: Container, context: ContextDict): + self._check_component(parent, context) + extras = self.get_extra_params(parent) + if extras: + raise ValueError("ComponentConverter %s can't serialize extra params %s", (self, extras)) + for value in self.get_value_list(parent): + output.append(self.meta.serialize_toplevel(value, context)) diff --git a/src/ics/converter/special.py b/src/ics/converter/special.py new file mode 100644 index 00000000..5d7a8e47 --- /dev/null +++ b/src/ics/converter/special.py @@ -0,0 +1,115 @@ +from datetime import tzinfo +from io import StringIO +from typing import List, TYPE_CHECKING + +from dateutil.rrule import rruleset +from dateutil.tz import tzical + +from ics.attendee import Attendee, Organizer, Person +from ics.converter.base import AttributeConverter +from ics.converter.component import ComponentConverter +from ics.grammar import Container, ContentLine +from ics.types import ContainerItem, ContextDict + +if TYPE_CHECKING: + from ics.component import Component + + +class TimezoneConverter(AttributeConverter): + @property + def filter_ics_names(self) -> List[str]: + return ["VTIMEZONE"] + + def populate(self, component: "Component", item: ContainerItem, context: ContextDict) -> bool: + assert isinstance(item, Container) + self._check_component(component, context) + + item = item.clone([ + line for line in item if not line.name.startswith("X-") and not line.name == "SEQUENCE" + ]) + + fake_file = StringIO() + fake_file.write(item.serialize()) # Represent the block as a string + fake_file.seek(0) + timezones = tzical(fake_file) # tzical does not like strings + + # timezones is a tzical object and could contain multiple timezones + print("got timezone", timezones.keys(), timezones.get()) + self.set_or_append_value(component, timezones.get()) + return True + + def serialize(self, component: "Component", output: Container, context: ContextDict): + for tz in self.get_value_list(component): + raise NotImplementedError("Timezones can't be serialized") + + +AttributeConverter.BY_TYPE[tzinfo] = TimezoneConverter + + +class RecurrenceConverter(AttributeConverter): + # TODO handle extras? + # TODO pass and handle available_tz / tzinfos + + @property + def filter_ics_names(self) -> List[str]: + return ["RRULE", "RDATE", "EXRULE", "EXDATE", "DTSTART"] + + def populate(self, component: "Component", item: ContainerItem, context: ContextDict) -> bool: + assert isinstance(item, ContentLine) + self._check_component(component, context) + # self.lines.append(item) + return False + + def finalize(self, component: "Component", context: ContextDict): + self._check_component(component, context) + # rrulestr("\r\n".join(self.lines), tzinfos={}, compatible=True) + + def serialize(self, component: "Component", output: Container, context: ContextDict): + pass + # value = rruleset() + # for rrule in value._rrule: + # output.append(ContentLine("RRULE", value=re.match("^RRULE:(.*)$", str(rrule)).group(1))) + # for exrule in value._exrule: + # output.append(ContentLine("EXRULE", value=re.match("^RRULE:(.*)$", str(exrule)).group(1))) + # for rdate in value._rdate: + # output.append(ContentLine(name="RDATE", value=DatetimeConverter.INST.serialize(rdate))) + # for exdate in value._exdate: + # output.append(ContentLine(name="EXDATE", value=DatetimeConverter.INST.serialize(exdate))) + + +AttributeConverter.BY_TYPE[rruleset] = RecurrenceConverter + + +class PersonConverter(AttributeConverter): + # TODO handle lists + + @property + def filter_ics_names(self) -> List[str]: + return [] + + def populate(self, component: "Component", item: ContainerItem, context: ContextDict) -> bool: + assert isinstance(item, ContentLine) + self._check_component(component, context) + return False + + def serialize(self, component: "Component", output: Container, context: ContextDict): + pass + + +AttributeConverter.BY_TYPE[Person] = PersonConverter +AttributeConverter.BY_TYPE[Attendee] = PersonConverter +AttributeConverter.BY_TYPE[Organizer] = PersonConverter + + +class AlarmConverter(ComponentConverter): + def populate(self, component: "Component", item: ContainerItem, context: ContextDict) -> bool: + # TODO handle trigger: Union[timedelta, datetime, None] before duration + assert isinstance(item, Container) + self._check_component(component, context) + + from ics.alarm import get_type_from_action + alarm_type = get_type_from_action(item) + instance = alarm_type() + alarm_type.Meta.populate_instance(instance, item, context) + self.set_or_append_value(component, instance) + return True diff --git a/src/ics/converter/timespan.py b/src/ics/converter/timespan.py new file mode 100644 index 00000000..74592ed8 --- /dev/null +++ b/src/ics/converter/timespan.py @@ -0,0 +1,125 @@ +from typing import List, TYPE_CHECKING, cast + +from ics.converter.base import AttributeConverter +from ics.grammar import Container, ContentLine +from ics.timespan import EventTimespan, Timespan, TodoTimespan +from ics.types import ContainerItem, ContextDict, ExtraParams, copy_extra_params +from ics.utils import ensure_datetime +from ics.valuetype.datetime import DateConverter, DatetimeConverter, DurationConverter + +if TYPE_CHECKING: + from ics.component import Component + +CONTEXT_BEGIN_TIME = "timespan_begin_time" +CONTEXT_END_TIME = "timespan_end_time" +CONTEXT_DURATION = "timespan_duration" +CONTEXT_PRECISION = "timespan_precision" +CONTEXT_END_NAME = "timespan_end_name" +CONTEXT_ITEMS = "timespan_items" +CONTEXT_KEYS = [CONTEXT_BEGIN_TIME, CONTEXT_END_TIME, CONTEXT_DURATION, + CONTEXT_PRECISION, CONTEXT_END_NAME, CONTEXT_ITEMS] + + +class TimespanConverter(AttributeConverter): + @property + def default_priority(self) -> int: + return 10000 + + @property + def filter_ics_names(self) -> List[str]: + return ["DTSTART", "DTEND", "DUE", "DURATION"] + + def populate(self, component: "Component", item: ContainerItem, context: ContextDict) -> bool: + assert isinstance(item, ContentLine) + self._check_component(component, context) + + seen_items = context.setdefault(CONTEXT_ITEMS, set()) + if item.name in seen_items: + raise ValueError("duplicate value for %s in %s" % (item.name, item)) + seen_items.add(item.name) + + params = copy_extra_params(item.params) + if item.name in ["DTSTART", "DTEND", "DUE"]: + value_type = params.pop("VALUE", ["DATE-TIME"]) + if value_type == ["DATE-TIME"]: + precision = "second" + elif value_type == ["DATE"]: + precision = "day" + else: + raise ValueError("can't handle %s with value type %s" % (item.name, value_type)) + + if context[CONTEXT_PRECISION] is None: + context[CONTEXT_PRECISION] = precision + else: + if context[CONTEXT_PRECISION] != precision: + raise ValueError("event with diverging begin and end time precision") + + if precision == "day": + value = DateConverter.INST.parse(item.value, params, context) + else: + assert precision == "second" + value = DatetimeConverter.INST.parse(item.value, params, context) + + if item.name == "DTSTART": + self.set_or_append_extra_params(component, params, name="begin") + context[CONTEXT_BEGIN_TIME] = value + else: + end_name = {"DTEND": "end", "DUE": "due"}[item.name] + context[CONTEXT_END_NAME] = end_name + self.set_or_append_extra_params(component, params, name=end_name) + context[CONTEXT_END_TIME] = value + + else: + assert item.name == "DURATION" + self.set_or_append_extra_params(component, params, name="duration") + context[CONTEXT_DURATION] = DurationConverter.INST.parse(item.value, params, context) + + return True + + def finalize(self, component: "Component", context: ContextDict): + self._check_component(component, context) + # missing values will be reported by the Timespan validator + timespan = self.value_type( + ensure_datetime(context[CONTEXT_BEGIN_TIME]), ensure_datetime(context[CONTEXT_END_TIME]), + context[CONTEXT_DURATION], context[CONTEXT_PRECISION]) + if context[CONTEXT_END_NAME] and context[CONTEXT_END_NAME] != timespan._end_name(): + raise ValueError("expected to get %s value, but got %s instead" + % (timespan._end_name(), context[CONTEXT_END_NAME])) + self.set_or_append_value(component, timespan) + super(TimespanConverter, self).finalize(component, context) + # we need to clear all values, otherwise they might not get overwritten by the next parsed Timespan + for key in CONTEXT_KEYS: + context.pop(key, None) + + def serialize(self, component: "Component", output: Container, context: ContextDict): + self._check_component(component, context) + value: Timespan = self.get_value(component) + if value.is_all_day(): + value_type = {"VALUE": ["DATE"]} + dt_conv = DateConverter.INST + else: + value_type = {} # implicit default is {"VALUE": ["DATE-TIME"]} + dt_conv = DatetimeConverter.INST + + if value.get_begin(): + params = copy_extra_params(cast(ExtraParams, self.get_extra_params(component, "begin"))) + params.update(value_type) + dt_value = dt_conv.serialize(value.get_begin(), params, context) + output.append(ContentLine(name="DTSTART", params=params, value=dt_value)) + + if value.get_end_representation() == "end": + end_name = {"end": "DTEND", "due": "DUE"}[value._end_name()] + params = copy_extra_params(cast(ExtraParams, self.get_extra_params(component, end_name))) + params.update(value_type) + dt_value = dt_conv.serialize(value.get_effective_end(), params, context) + output.append(ContentLine(name=end_name, params=params, value=dt_value)) + + elif value.get_end_representation() == "duration": + params = copy_extra_params(cast(ExtraParams, self.get_extra_params(component, "duration"))) + dur_value = DurationConverter.INST.serialize(value.get_effective_duration(), params, context) + output.append(ContentLine(name="DURATION", params=params, value=dur_value)) + + +AttributeConverter.BY_TYPE[Timespan] = TimespanConverter +AttributeConverter.BY_TYPE[EventTimespan] = TimespanConverter +AttributeConverter.BY_TYPE[TodoTimespan] = TimespanConverter diff --git a/src/ics/converter/value.py b/src/ics/converter/value.py new file mode 100644 index 00000000..dae59981 --- /dev/null +++ b/src/ics/converter/value.py @@ -0,0 +1,139 @@ +from typing import Any, List, TYPE_CHECKING, Tuple, cast + +import attr + +from ics.converter.base import AttributeConverter +from ics.grammar import Container, ContentLine +from ics.types import ContainerItem, ContextDict, ExtraParams, copy_extra_params +from ics.valuetype.base import ValueConverter + +if TYPE_CHECKING: + from ics.component import Component + + +@attr.s(frozen=True) +class AttributeValueConverter(AttributeConverter): + value_converters: List[ValueConverter] + + def __attrs_post_init__(self): + super(AttributeValueConverter, self).__attrs_post_init__() + object.__setattr__(self, "value_converters", []) + for value_type in self.value_types: + converter = ValueConverter.BY_TYPE.get(value_type, None) + if converter is None: + raise ValueError("can't convert %s with ValueConverter" % value_type) + self.value_converters.append(converter) + + @property + def filter_ics_names(self) -> List[str]: + return [self.ics_name] + + @property + def ics_name(self) -> str: + name = self.attribute.metadata.get("ics_name", None) + if not name: + name = self.attribute.name.upper().replace("_", "-").strip("-") + return name + + def __prepare_params(self, line: "ContentLine") -> Tuple[ExtraParams, ValueConverter]: + params = copy_extra_params(line.params) + value_type = params.pop("VALUE", None) + if value_type: + if len(value_type) != 1: + raise ValueError("multiple VALUE type definitions in %s" % line) + for converter in self.value_converters: + if converter.ics_type == value_type[0]: + break + else: + raise ValueError("can't convert %s with %s" % (line, self)) + else: + converter = self.value_converters[0] + return params, converter + + # TODO make storing/writing extra values/params configurably optional, but warn when information is lost + + def populate(self, component: "Component", item: ContainerItem, context: ContextDict) -> bool: + assert isinstance(item, ContentLine) + self._check_component(component, context) + if self.is_multi_value: + params, converter = self.__prepare_params(item) + for value in converter.split_value_list(item.value): + context[(self, "current_value_count")] += 1 + params = copy_extra_params(params) + parsed = converter.parse(value, params, context) # might modify params and context + params["__merge_next"] = ["TRUE"] + self.set_or_append_extra_params(component, params) + self.set_or_append_value(component, parsed) + if params is not None: + params["__merge_next"] = ["FALSE"] + else: + if context[(self, "current_value_count")] > 0: + raise ValueError("attribute %s can only be set once, second occurrence is %s" % (self.ics_name, item)) + context[(self, "current_value_count")] += 1 + params, converter = self.__prepare_params(item) + parsed = converter.parse(item.value, params, context) # might modify params and context + self.set_or_append_extra_params(component, params) + self.set_or_append_value(component, parsed) + return True + + def finalize(self, component: "Component", context: ContextDict): + self._check_component(component, context) + if self.is_required and context[(self, "current_value_count")] < 1: + raise ValueError("attribute %s is required but got no value" % self.ics_name) + super(AttributeValueConverter, self).finalize(component, context) + + def __find_value_converter(self, params: ExtraParams, value: Any) -> ValueConverter: + for nr, converter in enumerate(self.value_converters): + if not isinstance(value, converter.python_type): continue + if nr > 0: + params["VALUE"] = [converter.ics_type] + return converter + else: + raise ValueError("can't convert %s with %s" % (value, self)) + + def serialize(self, component: "Component", output: Container, context: ContextDict): + if self.is_multi_value: + self.__serialize_multi(component, output, context) + else: + value = self.get_value(component) + if value: + params = copy_extra_params(cast(ExtraParams, self.get_extra_params(component))) + converter = self.__find_value_converter(params, value) + serialized = converter.serialize(value, params, context) + output.append(ContentLine(name=self.ics_name, params=params, value=serialized)) + + def __serialize_multi(self, component: "Component", output: "Container", context: ContextDict): + extra_params = cast(List[ExtraParams], self.get_extra_params(component)) + values = self.get_value_list(component) + if len(extra_params) != len(values): + raise ValueError("length of extra params doesn't match length of parameters" + " for attribute %s of %r" % (self.attribute.name, component)) + + merge_next = False + current_params = None + current_values = [] + + for value, params in zip(values, extra_params): + merge_next = False + params = copy_extra_params(params) + if params.pop("__merge_next", None) == ["TRUE"]: + merge_next = True + converter = self.__find_value_converter(params, value) + serialized = converter.serialize(value, params, context) # might modify params and context + + if current_params is not None: + if current_params != params: + raise ValueError() + else: + current_params = params + + current_values.append(serialized) + + if not merge_next: + cl = ContentLine(name=self.ics_name, params=params, value=converter.join_value_list(current_values)) + output.append(cl) + current_params = None + current_values = [] + + if merge_next: + raise ValueError("last value in value list may not have merge_next set") diff --git a/ics/event.py b/src/ics/event.py similarity index 84% rename from ics/event.py rename to src/ics/event.py index b54f70e5..335f5e6a 100644 --- a/ics/event.py +++ b/src/ics/event.py @@ -1,63 +1,41 @@ from datetime import datetime, timedelta -from typing import Any, Dict, List, NamedTuple, Optional, Set, Tuple, Union, overload +from typing import Any, List, Optional, Tuple, Union import attr from attr.converters import optional as c_optional from attr.validators import in_, instance_of, optional as v_optional -from ics.alarm.base import BaseAlarm +from ics.alarm import BaseAlarm from ics.attendee import Attendee, Organizer from ics.component import Component -from ics.parsers.event_parser import EventParser -from ics.serializers.event_serializer import EventSerializer +from ics.converter.base import ics_attr_meta +from ics.converter.component import ComponentMeta +from ics.converter.timespan import TimespanConverter +from ics.geo import Geo, make_geo from ics.timespan import EventTimespan, Timespan -from ics.types import DatetimeLike, EventOrTimespan, EventOrTimespanOrInstant, TimedeltaLike, get_timespan_if_calendar_entry -from ics.utils import check_is_instance, ensure_datetime, ensure_timedelta, now_in_utc, uid_gen, validate_not_none +from ics.types import DatetimeLike, EventOrTimespan, EventOrTimespanOrInstant, TimedeltaLike, URL, get_timespan_if_calendar_entry +from ics.utils import check_is_instance, ensure_datetime, ensure_timedelta, ensure_utc, now_in_utc, uid_gen, validate_not_none STATUS_VALUES = (None, 'TENTATIVE', 'CONFIRMED', 'CANCELLED') -class Geo(NamedTuple): - latitude: float - longitude: float - - -@overload -def make_geo(value: None) -> None: - ... - - -@overload -def make_geo(value: Union[Dict[str, float], Tuple[float, float]]) -> "Geo": - ... - - -def make_geo(value): - if isinstance(value, dict): - return Geo(**value) - elif isinstance(value, tuple): - return Geo(*value) - else: - return None - - -@attr.s(repr=False, eq=True, order=False) +@attr.s(eq=True, order=False) class CalendarEntryAttrs(Component): - _timespan: Timespan = attr.ib(validator=instance_of(Timespan)) - name: Optional[str] = attr.ib(default=None) + _timespan: Timespan = attr.ib(validator=instance_of(Timespan), metadata=ics_attr_meta(converter=TimespanConverter)) + summary: Optional[str] = attr.ib(default=None) uid: str = attr.ib(factory=uid_gen) description: Optional[str] = attr.ib(default=None) location: Optional[str] = attr.ib(default=None) url: Optional[str] = attr.ib(default=None) - status: Optional[str] = attr.ib(default=None, converter=c_optional(str.upper), validator=v_optional(in_(STATUS_VALUES))) # type: ignore + status: Optional[str] = attr.ib(default=None, converter=c_optional(str.upper), validator=in_(STATUS_VALUES)) # type: ignore - # TODO these three timestamps must be in UTC according to the RFC - created: Optional[datetime] = attr.ib(default=None, converter=ensure_datetime) # type: ignore - last_modified: Optional[datetime] = attr.ib(default=None, converter=ensure_datetime) # type: ignore - dtstamp: datetime = attr.ib(factory=now_in_utc, converter=ensure_datetime, validator=validate_not_none) # type: ignore + created: Optional[datetime] = attr.ib(default=None, converter=ensure_utc) # type: ignore + last_modified: Optional[datetime] = attr.ib(default=None, converter=ensure_utc) # type: ignore + dtstamp: datetime = attr.ib(factory=now_in_utc, converter=ensure_utc, validator=validate_not_none) # type: ignore alarms: List[BaseAlarm] = attr.ib(factory=list, converter=list) + attach: List[Union[URL, bytes]] = attr.ib(factory=list, converter=list) def __init_subclass__(cls): super().__init_subclass__() @@ -169,17 +147,17 @@ def convert_timezone(self, tzinfo): def timespan(self) -> Timespan: return self._timespan - def __repr__(self) -> str: + def __str__(self) -> str: name = [self.__class__.__name__] - if self.name: - name.append("'%s'" % self.name) + if self.summary: + name.append("'%s'" % self.summary) prefix, _, suffix = self._timespan.get_str_segments() return "<%s>" % (" ".join(prefix + name + suffix)) #################################################################################################################### def cmp_tuple(self) -> Tuple[datetime, datetime, str]: - return (*self.timespan.cmp_tuple(), self.name or "") + return (*self.timespan.cmp_tuple(), self.summary or "") def __lt__(self, other: Any) -> bool: """self < other""" @@ -225,16 +203,16 @@ def is_included_in(self, second: EventOrTimespan) -> bool: return self._timespan.is_included_in(get_timespan_if_calendar_entry(second)) -@attr.s(repr=False, eq=True, order=False) # order methods are provided by CalendarEntryAttrs +@attr.s(eq=True, order=False) # order methods are provided by CalendarEntryAttrs class EventAttrs(CalendarEntryAttrs): classification: Optional[str] = attr.ib(default=None, validator=v_optional(instance_of(str))) transparent: Optional[bool] = attr.ib(default=None) organizer: Optional[Organizer] = attr.ib(default=None, validator=v_optional(instance_of(Organizer))) - geo: Optional[Geo] = attr.ib(default=None, converter=make_geo) # type: ignore + geo: Optional[Geo] = attr.ib(default=None, converter=make_geo) attendees: List[Attendee] = attr.ib(factory=list, converter=list) - categories: Set[str] = attr.ib(factory=set, converter=set) + categories: List[str] = attr.ib(factory=list, converter=list) def add_attendee(self, attendee: Attendee): """ Add an attendee to the attendees set """ @@ -256,14 +234,11 @@ class Event(EventAttrs): _timespan: EventTimespan = attr.ib(validator=instance_of(EventTimespan)) - class Meta: - name = "VEVENT" - parser = EventParser - serializer = EventSerializer + Meta = ComponentMeta("VEVENT") def __init__( self, - name: str = None, + summary: str = None, begin: DatetimeLike = None, end: DatetimeLike = None, duration: TimedeltaLike = None, @@ -279,4 +254,4 @@ def __init__( if (begin is not None or end is not None or duration is not None) and "timespan" in kwargs: raise ValueError("can't specify explicit timespan together with any of begin, end or duration") kwargs.setdefault("timespan", EventTimespan(ensure_datetime(begin), ensure_datetime(end), ensure_timedelta(duration))) - super(Event, self).__init__(kwargs.pop("timespan"), name, *args, **kwargs) + super(Event, self).__init__(kwargs.pop("timespan"), summary, *args, **kwargs) diff --git a/src/ics/geo.py b/src/ics/geo.py new file mode 100644 index 00000000..64040fce --- /dev/null +++ b/src/ics/geo.py @@ -0,0 +1,26 @@ +from typing import Dict, NamedTuple, Tuple, Union, overload + + +class Geo(NamedTuple): + latitude: float + longitude: float + # TODO also store params like comment? + + +@overload +def make_geo(value: None) -> None: + ... + + +@overload +def make_geo(value: Union[Dict[str, float], Tuple[float, float]]) -> "Geo": + ... + + +def make_geo(value): + if isinstance(value, dict): + return Geo(**value) + elif isinstance(value, tuple): + return Geo(*value) + else: + return None diff --git a/src/ics/grammar/__init__.py b/src/ics/grammar/__init__.py new file mode 100644 index 00000000..d8bc873d --- /dev/null +++ b/src/ics/grammar/__init__.py @@ -0,0 +1,299 @@ +import functools +import re +import warnings +from collections import UserString +from typing import Generator, List, MutableSequence, Union + +import attr +import importlib_resources # type: ignore +import tatsu # type: ignore +from tatsu.exceptions import FailedToken # type: ignore + +from ics.types import ContainerItem, ExtraParams, RuntimeAttrValidation, copy_extra_params +from ics.utils import limit_str_length, next_after_str_escape, validate_truthy + +__all__ = ["ParseError", "QuotedParamValue", "ContentLine", "Container", "string_to_container"] + +GRAMMAR = tatsu.compile(importlib_resources.read_text(__name__, "contentline.ebnf")) + + +class ParseError(Exception): + pass + + +class QuotedParamValue(UserString): + pass + + +@attr.s +class ContentLine(RuntimeAttrValidation): + """ + Represents one property line. + + For example: + + ``FOO;BAR=1:YOLO`` is represented by + + ``ContentLine('FOO', {'BAR': ['1']}, 'YOLO'))`` + """ + + name: str = attr.ib(converter=str.upper) # type: ignore + params: ExtraParams = attr.ib(factory=lambda: ExtraParams(dict())) + value: str = attr.ib(default="") + + # TODO store value type for jCal and line number for error messages + + def serialize(self): + return "".join(self.serialize_iter()) + + def serialize_iter(self, newline=False): + yield self.name + for pname in self.params: + yield ";" + yield pname + yield "=" + for nr, pval in enumerate(self.params[pname]): + if nr > 0: + yield "," + if isinstance(pval, QuotedParamValue) or re.search("[:;,]", pval): + # Property parameter values that contain the COLON, SEMICOLON, or COMMA character separators + # MUST be specified as quoted-string text values. + # TODO The DQUOTE character is used as a delimiter for parameter values that contain + # restricted characters or URI text. + # TODO Property parameter values that are not in quoted-strings are case-insensitive. + yield '"%s"' % escape_param(pval) + else: + yield escape_param(pval) + yield ":" + yield self.value + if newline: + yield "\r\n" + + def __getitem__(self, item): + return self.params[item] + + def __setitem__(self, item, values): + self.params[item] = list(values) + + @classmethod + def parse(cls, line): + """Parse a single iCalendar-formatted line into a ContentLine""" + if "\n" in line or "\r" in line: + raise ValueError("ContentLine can only contain escaped newlines") + try: + ast = GRAMMAR.parse(line, whitespace="") + except FailedToken: + raise ParseError() + else: + return cls.interpret_ast(ast) + + @classmethod + def interpret_ast(cls, ast): + name = ast['name'] + value = ast['value'] + params = ExtraParams(dict()) + for param_ast in ast.get('params', []): + param_name = param_ast["name"] + params[param_name] = [] + for param_value_ast in param_ast["values_"]: + val = unescape_param(param_value_ast["value"]) + if param_value_ast["quoted"] == "true": + val = QuotedParamValue(val) + params[param_name].append(val) + return cls(name, params, value) + + def clone(self): + """Makes a copy of itself""" + return attr.evolve(self, params=copy_extra_params(self.params)) + + def __str__(self): + return "%s%s='%s'" % (self.name, self.params or "", limit_str_length(self.value)) + + +def _wrap_list_func(list_func): + @functools.wraps(list_func) + def wrapper(self, *args, **kwargs): + return list_func(self.data, *args, **kwargs) + + return wrapper + + +@attr.s(repr=False) +class Container(MutableSequence[ContainerItem]): + """Represents an iCalendar object. + Contains a list of ContentLines or Containers. + + Args: + + name: the name of the object (VCALENDAR, VEVENT etc.) + items: Containers or ContentLines + """ + + name: str = attr.ib(converter=str.upper, validator=validate_truthy) # type:ignore + data: List[ContainerItem] = attr.ib(converter=list, default=[], + validator=lambda inst, attr, value: inst.check_items(*value)) + + def __str__(self): + return "%s[%s]" % (self.name, ", ".join(str(cl) for cl in self.data)) + + def __repr__(self): + return "%s(%r, %s)" % (type(self).__name__, self.name, repr(self.data)) + + def serialize(self): + return "".join(self.serialize_iter()) + + def serialize_iter(self, newline=False): + yield "BEGIN:" + yield self.name + yield "\r\n" + for line in self: + yield from line.serialize_iter(newline=True) + yield "END:" + yield self.name + if newline: + yield "\r\n" + + @classmethod + def parse(cls, name, tokenized_lines): + items = [] + if not name.isupper(): + warnings.warn("Container 'BEGIN:%s' is not all-uppercase" % name) + for line in tokenized_lines: + if line.name == 'BEGIN': + items.append(cls.parse(line.value, tokenized_lines)) + elif line.name == 'END': + if line.value.upper() != name.upper(): + raise ParseError( + "Expected END:{}, got END:{}".format(name, line.value)) + if not name.isupper(): + warnings.warn("Container 'END:%s' is not all-uppercase" % name) + break + else: + items.append(line) + else: # if break was not called + raise ParseError("Missing END:{}".format(name)) + return cls(name, items) + + def clone(self, items=None, deep=False): + """Makes a copy of itself""" + if items is None: + items = self.data + if deep: + items = (item.clone() for item in items) + return attr.evolve(self, data=items) + + @staticmethod + def check_items(*items): + from ics.utils import check_is_instance + if len(items) == 1: + check_is_instance("item", items[0], (ContentLine, Container)) + else: + for nr, item in enumerate(items): + check_is_instance("item %s" % nr, item, (ContentLine, Container)) + + def __setitem__(self, index, value): # index might be slice and value might be iterable + self.data.__setitem__(index, value) + attr.validate(self) + + def insert(self, index, value): + self.check_items(value) + self.data.insert(index, value) + + def append(self, value): + self.check_items(value) + self.data.append(value) + + def extend(self, values): + self.data.extend(values) + attr.validate(self) + + def __getitem__(self, i): + if isinstance(i, slice): + return attr.evolve(self, data=self.data[i]) + else: + return self.data[i] + + __contains__ = _wrap_list_func(list.__contains__) + __delitem__ = _wrap_list_func(list.__delitem__) + __iter__ = _wrap_list_func(list.__iter__) + __len__ = _wrap_list_func(list.__len__) + __reversed__ = _wrap_list_func(list.__reversed__) + clear = _wrap_list_func(list.clear) + count = _wrap_list_func(list.count) + index = _wrap_list_func(list.index) + pop = _wrap_list_func(list.pop) + remove = _wrap_list_func(list.remove) + reverse = _wrap_list_func(list.reverse) + + +def escape_param(string: Union[str, QuotedParamValue]) -> str: + return str(string).translate( + {ord("\""): "^'", + ord("^"): "^^", + ord("\n"): "^n", + ord("\r"): ""}) + + +def unescape_param(string: str) -> str: + return "".join(unescape_param_iter(string)) + + +def unescape_param_iter(string: str) -> Generator[str, None, None]: + it = iter(string) + for c1 in it: + if c1 == "^": + c2 = next_after_str_escape(it, full_str=string) + if c2 == "n": + yield "\n" + elif c2 == "^": + yield "^" + elif c2 == "'": + yield "\"" + else: + yield c1 + yield c2 + else: + yield c1 + + +def unfold_lines(physical_lines): + current_line = '' + for line in physical_lines: + line = line.rstrip('\r') + if not current_line: + current_line = line + elif line and line[0] in (' ', '\t'): + current_line += line[1:] + else: + if len(current_line) > 0: + yield current_line + current_line = line + if current_line: + yield current_line + + +def tokenize_line(unfolded_lines): + for line in unfolded_lines: + yield ContentLine.parse(line) + + +def parse(tokenized_lines): + # tokenized_lines must be an iterator, so that Container.parse can consume/steal lines + tokenized_lines = iter(tokenized_lines) + res = [] + for line in tokenized_lines: + if line.name == 'BEGIN': + res.append(Container.parse(line.value, tokenized_lines)) + else: + res.append(line) + return res + + +def lines_to_container(lines): + return parse(tokenize_line(unfold_lines(lines))) + + +def string_to_container(txt): + # unicode newlines are interpreted as such by str.splitlines(), but not by the ics standard + # "A:abc\x85def".splitlines() => ['A:abc', 'def'] which is wrong + return lines_to_container(re.split("\r?\n|\r", txt)) diff --git a/ics/grammar/contentline.ebnf b/src/ics/grammar/contentline.ebnf similarity index 92% rename from ics/grammar/contentline.ebnf rename to src/ics/grammar/contentline.ebnf index c033b322..5a5224ff 100644 --- a/ics/grammar/contentline.ebnf +++ b/src/ics/grammar/contentline.ebnf @@ -30,7 +30,7 @@ contentline = name:name {(";" params+:param )}* ":" value:value ; param = name:param_name "=" values+:param_value {("," values+:param_value)}* ; param_name = iana_token | x_name ; -param_value = quoted_string | paramtext ; +param_value = value:quoted_string quoted:`true` | value:paramtext quoted:`false` ; paramtext = SAFE_CHAR_STAR ; value = VALUE_CHAR_STAR ; diff --git a/ics/icalendar.py b/src/ics/icalendar.py similarity index 59% rename from ics/icalendar.py rename to src/ics/icalendar.py index 1e2afeee..31ab4df4 100644 --- a/ics/icalendar.py +++ b/src/ics/icalendar.py @@ -1,13 +1,13 @@ -from typing import Dict, Iterable, List, Optional, Union +from datetime import tzinfo +from typing import ClassVar, Iterable, Iterator, List, Optional, Union import attr from attr.validators import instance_of from ics.component import Component +from ics.converter.component import ComponentMeta from ics.event import Event -from ics.grammar.parse import Container, calendar_string_to_containers -from ics.parsers.icalendar_parser import CalendarParser -from ics.serializers.icalendar_serializer import CalendarSerializer +from ics.grammar import Container, string_to_container from ics.timeline import Timeline from ics.todo import Todo @@ -19,12 +19,7 @@ class CalendarAttrs(Component): scale: Optional[str] = attr.ib(default=None) method: Optional[str] = attr.ib(default=None) - version_params: Dict[str, List[str]] = attr.ib(factory=dict) - prodid_params: Dict[str, List[str]] = attr.ib(factory=dict) - scale_params: Dict[str, List[str]] = attr.ib(factory=dict) - method_params: Dict[str, List[str]] = attr.ib(factory=dict) - - _timezones: Dict = attr.ib(factory=dict, init=False, repr=False, eq=False, order=False, hash=False) + _timezones: List[tzinfo] = attr.ib(factory=list, converter=list) # , init=False, repr=False, eq=False, order=False, hash=False) events: List[Event] = attr.ib(factory=list, converter=list) todos: List[Todo] = attr.ib(factory=list, converter=list) @@ -41,17 +36,13 @@ class Calendar(CalendarAttrs): """ - class Meta: - name = 'VCALENDAR' - parser = CalendarParser - serializer = CalendarSerializer - - DEFAULT_VERSION = "2.0" - DEFAULT_PRODID = "ics.py - http://git.io/lLljaA" + Meta = ComponentMeta("VCALENDAR") + DEFAULT_VERSION: ClassVar[str] = "2.0" + DEFAULT_PRODID: ClassVar[str] = "ics.py - http://git.io/lLljaA" def __init__( self, - imports: Union[str, Container] = None, + imports: Union[str, Container, None] = None, events: Optional[Iterable[Event]] = None, todos: Optional[Iterable[Todo]] = None, creator: str = None, @@ -69,21 +60,20 @@ def __init__( events = tuple() if todos is None: todos = tuple() - kwargs.setdefault("version", self.Meta.DEFAULT_VERSION) - kwargs.setdefault("prodid", creator if creator is not None else self.Meta.DEFAULT_PRODID) + kwargs.setdefault("version", self.DEFAULT_VERSION) + kwargs.setdefault("prodid", creator if creator is not None else self.DEFAULT_PRODID) super(Calendar, self).__init__(events=events, todos=todos, **kwargs) # type: ignore self.timeline = Timeline(self, None) if imports is not None: if isinstance(imports, Container): - self._populate(imports) + self.populate(imports) else: - containers = calendar_string_to_containers(imports) + containers = string_to_container(imports) if len(containers) != 1: - raise NotImplementedError( - 'Multiple calendars in one file are not supported by this method. Use ics.Calendar.parse_multiple()') - - self._populate(containers[0]) # Use first calendar + raise ValueError("Multiple calendars in one file are not supported by this method." + "Use ics.Calendar.parse_multiple()") + self.populate(containers[0]) @property def creator(self) -> str: @@ -99,17 +89,17 @@ def parse_multiple(cls, string): Parses an input string that may contain mutiple calendars and retruns a list of :class:`ics.event.Calendar` """ - containers = calendar_string_to_containers(string) + containers = string_to_container(string) return [cls(imports=c) for c in containers] - def __repr__(self) -> str: - return "" \ - .format(len(self.events), - "s" if len(self.events) > 1 else "", - len(self.todos), - "s" if len(self.todos) > 1 else "") + def __str__(self) -> str: + return "".format( + len(self.events), + "s" if len(self.events) > 1 else "", + len(self.todos), + "s" if len(self.todos) > 1 else "") - def __iter__(self) -> Iterable[str]: + def __iter__(self) -> Iterator[str]: """Returns: iterable: an iterable version of __str__, line per line (with line-endings). @@ -117,7 +107,7 @@ def __iter__(self) -> Iterable[str]: Example: Can be used to write calendar to a file: - >>> c = Calendar(); c.events.append(Event(name="My cool event")) + >>> c = Calendar(); c.events.append(Event(summary="My cool event")) >>> open('my.ics', 'w').writelines(c) """ - return iter(str(self).splitlines(keepends=True)) + return iter(self.serialize().splitlines(keepends=True)) diff --git a/ics/timeline.py b/src/ics/timeline.py similarity index 100% rename from ics/timeline.py rename to src/ics/timeline.py diff --git a/ics/timespan.py b/src/ics/timespan.py similarity index 89% rename from ics/timespan.py rename to src/ics/timespan.py index 749488ab..1fdd0173 100644 --- a/ics/timespan.py +++ b/src/ics/timespan.py @@ -1,33 +1,40 @@ from datetime import datetime, timedelta, tzinfo as TZInfo -from typing import Any, NamedTuple, Optional, TypeVar, Union, cast, overload +from typing import Any, Callable, NamedTuple, Optional, TYPE_CHECKING, TypeVar, Union, cast, overload import attr from attr.validators import instance_of, optional as v_optional from dateutil.tz import tzlocal from ics.types import DatetimeLike -from ics.utils import TIMEDELTA_CACHE, ceil_datetime_to_midnight, ensure_datetime, floor_datetime_to_midnight, timedelta_nearly_zero +from ics.utils import TIMEDELTA_CACHE, TIMEDELTA_DAY, TIMEDELTA_ZERO, ceil_datetime_to_midnight, ensure_datetime, \ + floor_datetime_to_midnight, timedelta_nearly_zero + +if TYPE_CHECKING: + # Literal is new in python 3.8, but backported via typing_extensions + # we don't need typing_extensions as actual (dev-)dependency as mypy has builtin support + from typing_extensions import Literal @attr.s class Normalization(object): normalize_floating: bool = attr.ib() normalize_with_tz: bool = attr.ib() - replacement: Union[TZInfo, None] = attr.ib() + replacement: Union[TZInfo, Callable[[], TZInfo], None] = attr.ib() @overload def normalize(self, value: "Timespan") -> "Timespan": ... - @overload # noqa + # pyflakes < 2.2 reports 'redefinition of unused' for overloaded class members + @overload def normalize(self, value: DatetimeLike) -> datetime: ... - @overload # noqa + @overload def normalize(self, value: None) -> None: ... - def normalize(self, value): # noqa + def normalize(self, value): """ Normalize datetime or timespan instances to make naive/floating ones (without timezone, i.e. tzinfo == None) comparable to aware ones with a fixed timezone. @@ -47,13 +54,17 @@ def normalize(self, value): # noqa normalize = (floating and self.normalize_floating) or (not floating and self.normalize_with_tz) if normalize: - return replace_timezone(value, self.replacement) + replacement = self.replacement + if callable(replacement): + replacement = replacement() + return replace_timezone(value, replacement) else: return value -CMP_DATETIME_NONE_DEFAULT = datetime.min -CMP_NORMALIZATION = Normalization(normalize_floating=True, normalize_with_tz=False, replacement=tzlocal()) +# using datetime.min might lead to problems when doing timezone conversions / comparisions (e.g. by substracting an 1 hour offset) +CMP_DATETIME_NONE_DEFAULT = datetime(1900, 1, 1, 0, 0) +CMP_NORMALIZATION = Normalization(normalize_floating=True, normalize_with_tz=False, replacement=tzlocal) TimespanTuple = NamedTuple("TimespanTuple", [("begin", datetime), ("end", datetime)]) NullableTimespanTuple = NamedTuple("NullableTimespanTuple", [("begin", Optional[datetime]), ("end", Optional[datetime])]) @@ -76,10 +87,10 @@ def __attrs_post_init__(self): def replace( self: TimespanT, - begin_time: Optional[datetime] = False, # type: ignore - end_time: Optional[datetime] = False, # type: ignore - duration: Optional[timedelta] = False, # type: ignore - precision: str = False # type: ignore + begin_time: Union[datetime, None, "Literal[False]"] = False, + end_time: Union[datetime, None, "Literal[False]"] = False, + duration: Union[timedelta, None, "Literal[False]"] = False, + precision: Union[str, "Literal[False]"] = False ) -> TimespanT: if begin_time is False: begin_time = self.begin_time @@ -89,7 +100,10 @@ def replace( duration = self.duration if precision is False: precision = self.precision - return type(self)(begin_time=begin_time, end_time=end_time, duration=duration, precision=precision) + return type(self)(begin_time=cast(Optional[datetime], begin_time), + end_time=cast(Optional[datetime], end_time), + duration=cast(Optional[timedelta], duration), + precision=cast(str, precision)) def replace_timezone(self: TimespanT, tzinfo: Optional[TZInfo]) -> TimespanT: if self.is_all_day(): @@ -130,7 +144,7 @@ def validate_timeprecision(value, name): validate_timeprecision(self.end_time, self._end_name()) if self.begin_time > self.end_time: raise ValueError("begin time must be before " + self._end_name() + " time") - if self.precision == "day" and self.end_time < (self.begin_time + TIMEDELTA_CACHE["day"]): + if self.precision == "day" and self.end_time < (self.begin_time + TIMEDELTA_DAY): raise ValueError("all-day timespan duration must be at least one day") if self.duration is not None: raise ValueError("can't set duration together with " + self._end_name() + " time") @@ -144,9 +158,9 @@ def validate_timeprecision(value, name): (self.get_effective_duration(), self.precision)) if self.duration is not None: - if self.duration < TIMEDELTA_CACHE[0]: + if self.duration < TIMEDELTA_ZERO: raise ValueError("timespan duration must be positive") - if self.precision == "day" and self.duration < TIMEDELTA_CACHE["day"]: + if self.precision == "day" and self.duration < TIMEDELTA_DAY: raise ValueError("all-day timespan duration must be at least one day") if not timedelta_nearly_zero(self.duration % TIMEDELTA_CACHE[self.precision]): raise ValueError("duration value %s has higher precision than set precision %s" % @@ -219,7 +233,7 @@ def make_all_day(self) -> "Timespan": if end is not None: end = ceil_datetime_to_midnight(end).replace(tzinfo=None) if end == begin: # we also add another day if the duration would be 0 otherwise - end = end + TIMEDELTA_CACHE["day"] + end = end + TIMEDELTA_DAY if self.get_end_representation() == "duration": assert end is not None @@ -298,11 +312,11 @@ def has_explicit_end(self) -> bool: def timespan_tuple(self, default: None = None, normalization: Normalization = None) -> NullableTimespanTuple: ... - @overload # noqa + @overload def timespan_tuple(self, default: datetime, normalization: Normalization = None) -> TimespanTuple: ... - def timespan_tuple(self, default=None, normalization=None): # noqa + def timespan_tuple(self, default=None, normalization=None): if normalization: return TimespanTuple( normalization.normalize(self.get_begin() or default), @@ -315,7 +329,7 @@ def timespan_tuple(self, default=None, normalization=None): # noqa ) def cmp_tuple(self) -> TimespanTuple: - return self.timespan_tuple(default=datetime.min, normalization=CMP_NORMALIZATION) + return self.timespan_tuple(default=CMP_DATETIME_NONE_DEFAULT, normalization=CMP_NORMALIZATION) def __require_tuple_components(self, values, *required): for nr, (val, req) in enumerate(zip(values, required)): @@ -418,9 +432,9 @@ def get_effective_duration(self) -> timedelta: elif self.end_time is not None and self.begin_time is not None: return self.end_time - self.begin_time elif self.is_all_day(): - return TIMEDELTA_CACHE["day"] + return TIMEDELTA_DAY else: - return TIMEDELTA_CACHE[0] + return TIMEDELTA_ZERO class TodoTimespan(Timespan): diff --git a/ics/todo.py b/src/ics/todo.py similarity index 83% rename from ics/todo.py rename to src/ics/todo.py index 3eb94043..16a03b22 100644 --- a/ics/todo.py +++ b/src/ics/todo.py @@ -10,9 +10,8 @@ import attr from attr.validators import in_, instance_of, optional as v_optional +from ics.converter.component import ComponentMeta from ics.event import CalendarEntryAttrs -from ics.parsers.todo_parser import TodoParser -from ics.serializers.todo_serializer import TodoSerializer from ics.timespan import TodoTimespan from ics.types import DatetimeLike, TimedeltaLike from ics.utils import ensure_datetime, ensure_timedelta @@ -34,11 +33,11 @@ def wrapper(*args, **kwargs): return wrapper -@attr.s(repr=False, eq=True, order=False) # order methods are provided by CalendarEntryAttrs +@attr.s(eq=True, order=False) # order methods are provided by CalendarEntryAttrs class TodoAttrs(CalendarEntryAttrs): percent: Optional[int] = attr.ib(default=None, validator=v_optional(in_(range(0, MAX_PERCENT + 1)))) priority: Optional[int] = attr.ib(default=None, validator=v_optional(in_(range(0, MAX_PRIORITY + 1)))) - completed: Optional[datetime] = attr.ib(default=None, converter=ensure_datetime) # type: ignore + completed: Optional[datetime] = attr.ib(default=None, converter=ensure_datetime) class Todo(TodoAttrs): @@ -49,10 +48,7 @@ class Todo(TodoAttrs): """ _timespan: TodoTimespan = attr.ib(validator=instance_of(TodoTimespan)) - class Meta: - name = "VTODO" - parser = TodoParser - serializer = TodoSerializer + Meta = ComponentMeta("VTODO") def __init__( self, @@ -73,14 +69,14 @@ def convert_due(self, representation): representation = "end" super(Todo, self).convert_end(representation) - due = property(TodoAttrs.end.fget, TodoAttrs.end.fset) # type: ignore + due = property(TodoAttrs.end.fget, TodoAttrs.end.fset) # convert_due = TodoAttrs.convert_end # see above - due_representation = property(TodoAttrs.end_representation.fget) # type: ignore - has_explicit_due = property(TodoAttrs.has_explicit_end.fget) # type: ignore + due_representation = property(TodoAttrs.end_representation.fget) + has_explicit_due = property(TodoAttrs.has_explicit_end.fget) due_within = TodoAttrs.ends_within - end = property(deprecated_due(TodoAttrs.end.fget), deprecated_due(TodoAttrs.end.fset)) # type: ignore + end = property(deprecated_due(TodoAttrs.end.fget), deprecated_due(TodoAttrs.end.fset)) convert_end = deprecated_due(TodoAttrs.convert_end) - end_representation = property(deprecated_due(TodoAttrs.end_representation.fget)) # type: ignore - has_explicit_end = property(deprecated_due(TodoAttrs.has_explicit_end.fget)) # type: ignore + end_representation = property(deprecated_due(TodoAttrs.end_representation.fget)) + has_explicit_end = property(deprecated_due(TodoAttrs.has_explicit_end.fget)) ends_within = deprecated_due(TodoAttrs.ends_within) diff --git a/src/ics/types.py b/src/ics/types.py new file mode 100644 index 00000000..a194c352 --- /dev/null +++ b/src/ics/types.py @@ -0,0 +1,176 @@ +import functools +import warnings +from datetime import date, datetime, timedelta +from typing import Any, Dict, Iterator, List, MutableMapping, NewType, Optional, TYPE_CHECKING, Tuple, Union, cast, overload +from urllib.parse import ParseResult + +import attr + +if TYPE_CHECKING: + # noinspection PyUnresolvedReferences + from ics.event import Event, CalendarEntryAttrs + # noinspection PyUnresolvedReferences + from ics.todo import Todo + # noinspection PyUnresolvedReferences + from ics.timespan import Timespan + # noinspection PyUnresolvedReferences + from ics.grammar import ContentLine, Container + +__all__ = [ + "ContainerItem", "ContainerList", "URL", + + "DatetimeLike", "OptionalDatetimeLike", + "TimedeltaLike", "OptionalTimedeltaLike", + + "TimespanOrBegin", + "EventOrTimespan", + "EventOrTimespanOrInstant", + "TodoOrTimespan", + "TodoOrTimespanOrInstant", + "CalendarEntryOrTimespan", + "CalendarEntryOrTimespanOrInstant", + + "get_timespan_if_calendar_entry", + + "RuntimeAttrValidation", + + "EmptyDict", "ExtraParams", "EmptyParams", "ContextDict", "EmptyContext", "copy_extra_params", +] + +ContainerItem = Union["ContentLine", "Container"] +ContainerList = List[ContainerItem] +URL = ParseResult + +DatetimeLike = Union[Tuple, Dict, datetime, date] +OptionalDatetimeLike = Union[Tuple, Dict, datetime, date, None] +TimedeltaLike = Union[Tuple, Dict, timedelta] +OptionalTimedeltaLike = Union[Tuple, Dict, timedelta, None] + +TimespanOrBegin = Union[datetime, date, "Timespan"] +EventOrTimespan = Union["Event", "Timespan"] +EventOrTimespanOrInstant = Union["Event", "Timespan", datetime] +TodoOrTimespan = Union["Todo", "Timespan"] +TodoOrTimespanOrInstant = Union["Todo", "Timespan", datetime] +CalendarEntryOrTimespan = Union["CalendarEntryAttrs", "Timespan"] +CalendarEntryOrTimespanOrInstant = Union["CalendarEntryAttrs", "Timespan", datetime] + + +@overload +def get_timespan_if_calendar_entry(value: CalendarEntryOrTimespan) -> "Timespan": + ... + + +@overload +def get_timespan_if_calendar_entry(value: datetime) -> datetime: + ... + + +@overload +def get_timespan_if_calendar_entry(value: None) -> None: + ... + + +def get_timespan_if_calendar_entry(value): + from ics.event import CalendarEntryAttrs # noqa: F811 # pyflakes considers this a redef of the unused if TYPE_CHECKING import above + + if isinstance(value, CalendarEntryAttrs): + return value._timespan + else: + return value + + +@attr.s +class RuntimeAttrValidation(object): + """ + Mixin that automatically calls the converters and validators of `attr` attributes. + The library itself only calls these in the generated `__init__` method, with + this mixin they are also called when later (re-)assigning an attribute, which + is handled by `__setattr__`. This makes setting attributes as versatile as specifying + them as init parameters and also ensures that the guarantees of validators are + preserved even after creation of the object, at a small runtime cost. + """ + + def __attrs_post_init__(self): + self.__post_init__ = True + + def __setattr__(self, key, value): + if getattr(self, "__post_init__", None): + cls = self.__class__ # type: Any + if not getattr(cls, "__attr_fields__", None): + cls.__attr_fields__ = attr.fields_dict(cls) + try: + field = cls.__attr_fields__[key] + except KeyError: + pass + else: # when no KeyError was thrown + if field.converter is not None: + value = field.converter(value) + if field.validator is not None: + field.validator(self, field, value) + super(RuntimeAttrValidation, self).__setattr__(key, value) + + +class EmptyDictType(MutableMapping[Any, None]): + """An empty, immutable dict that returns `None` for any key. Useful as default value for function arguments.""" + + def __getitem__(self, k: Any) -> None: + return None + + def __setitem__(self, k: Any, v: None) -> None: + warnings.warn("%s[%r] = %s ignored" % (self.__class__.__name__, k, v)) + return + + def __delitem__(self, v: Any) -> None: + warnings.warn("del %s[%r] ignored" % (self.__class__.__name__, v)) + return + + def __len__(self) -> int: + return 0 + + def __iter__(self) -> Iterator[Any]: + return iter([]) + + +EmptyDict = EmptyDictType() +ExtraParams = NewType("ExtraParams", Dict[str, List[str]]) +EmptyParams = cast("ExtraParams", EmptyDict) +ContextDict = NewType("ContextDict", Dict[Any, Any]) +EmptyContext = cast("ContextDict", EmptyDict) + + +def copy_extra_params(old: Optional[ExtraParams]) -> ExtraParams: + new: ExtraParams = ExtraParams(dict()) + if not old: + return new + for key, value in old.items(): + if isinstance(value, str): + new[key] = value + elif isinstance(value, list): + new[key] = list(value) + else: + raise ValueError("can't convert extra param %s with value of type %s: %s" % (key, type(value), value)) + return new + + +def attrs_custom_init(cls): + assert attr.has(cls) + attr_init = cls.__init__ + custom_init = cls.__attr_custom_init__ + + @functools.wraps(attr_init) + def new_init(self, *args, **kwargs): + custom_init(self, attr_init, *args, **kwargs) + + cls.__init__ = new_init + cls.__attr_custom_init__ = None + del cls.__attr_custom_init__ + return cls + +# @attrs_custom_init +# @attr.s +# class Test(object): +# val1 = attr.ib() +# val2 = attr.ib() +# +# def __attr_custom_init__(self, attr_init, val1, val1_suffix, *args, **kwargs): +# attr_init(self, val1 + val1_suffix, *args, **kwargs) diff --git a/src/ics/utils.py b/src/ics/utils.py new file mode 100644 index 00000000..38153e20 --- /dev/null +++ b/src/ics/utils.py @@ -0,0 +1,228 @@ +from datetime import date, datetime, time, timedelta, timezone +from typing import overload +from uuid import uuid4 + +from dateutil.tz import UTC as dateutil_tzutc + +from ics.types import DatetimeLike, TimedeltaLike + +datetime_tzutc = timezone.utc + +MIDNIGHT = time() +TIMEDELTA_ZERO = timedelta() +TIMEDELTA_DAY = timedelta(days=1) +TIMEDELTA_SECOND = timedelta(seconds=1) +TIMEDELTA_CACHE = { + 0: TIMEDELTA_ZERO, + "day": TIMEDELTA_DAY, + "second": TIMEDELTA_SECOND +} +MAX_TIMEDELTA_NEARLY_ZERO = timedelta(seconds=1) / 2 + + +@overload +def ensure_datetime(value: None) -> None: ... + + +@overload +def ensure_datetime(value: DatetimeLike) -> datetime: ... + + +def ensure_datetime(value): + if value is None: + return None + elif isinstance(value, datetime): + return value + elif isinstance(value, date): + return datetime.combine(value, MIDNIGHT, tzinfo=None) + elif isinstance(value, tuple): + return datetime(*value) + elif isinstance(value, dict): + return datetime(**value) + else: + raise ValueError("can't construct datetime from %s" % repr(value)) + + +@overload +def ensure_utc(value: None) -> None: ... + + +@overload +def ensure_utc(value: DatetimeLike) -> datetime: ... + + +def ensure_utc(value): + value = ensure_datetime(value) + if value is not None: + value = value.astimezone(dateutil_tzutc) + return value + + +def now_in_utc() -> datetime: + return datetime.now(tz=dateutil_tzutc) + + +def is_utc(instant: datetime) -> bool: + tz = instant.tzinfo + if tz is None: + return False + if tz in [dateutil_tzutc, datetime_tzutc]: + return True + tzname = tz.tzname(instant) + if tzname and tzname.upper() == "UTC": + return True + return False + + +@overload +def ensure_timedelta(value: None) -> None: ... + + +@overload +def ensure_timedelta(value: TimedeltaLike) -> timedelta: ... + + +def ensure_timedelta(value): + if value is None: + return None + elif isinstance(value, timedelta): + return value + elif isinstance(value, tuple): + return timedelta(*value) + elif isinstance(value, dict): + return timedelta(**value) + else: + raise ValueError("can't construct timedelta from %s" % repr(value)) + + +############################################################################### +# Rounding Utils + +def timedelta_nearly_zero(td: timedelta) -> bool: + return -MAX_TIMEDELTA_NEARLY_ZERO <= td <= MAX_TIMEDELTA_NEARLY_ZERO + + +@overload +def floor_datetime_to_midnight(value: datetime) -> datetime: ... + + +@overload +def floor_datetime_to_midnight(value: date) -> date: ... + + +@overload +def floor_datetime_to_midnight(value: None) -> None: ... + + +def floor_datetime_to_midnight(value): + if value is None: + return None + if isinstance(value, date) and not isinstance(value, datetime): + return value + return datetime.combine(ensure_datetime(value).date(), MIDNIGHT, tzinfo=value.tzinfo) + + +@overload +def ceil_datetime_to_midnight(value: datetime) -> datetime: ... + + +@overload +def ceil_datetime_to_midnight(value: date) -> date: ... + + +@overload +def ceil_datetime_to_midnight(value: None) -> None: ... + + +def ceil_datetime_to_midnight(value): + if value is None: + return None + if isinstance(value, date) and not isinstance(value, datetime): + return value + floored = floor_datetime_to_midnight(value) + if floored != value: + return floored + TIMEDELTA_DAY + else: + return floored + + +def floor_timedelta_to_days(value: timedelta) -> timedelta: + return value - (value % TIMEDELTA_DAY) + + +def ceil_timedelta_to_days(value: timedelta) -> timedelta: + mod = value % TIMEDELTA_DAY + if mod == TIMEDELTA_ZERO: + return value + else: + return value + TIMEDELTA_DAY - mod + + +############################################################################### +# String Utils + + +def limit_str_length(val): + return str(val) # TODO limit_str_length + + +def next_after_str_escape(it, full_str): + try: + return next(it) + except StopIteration as e: + raise ValueError("value '%s' may not end with an escape sequence" % full_str) from e + + +def uid_gen() -> str: + uid = str(uuid4()) + return "{}@{}.org".format(uid, uid[:4]) + + +############################################################################### + +def validate_not_none(inst, attr, value): + if value is None: + raise ValueError( + "'{name}' may not be None".format( + name=attr.name + ) + ) + + +def validate_truthy(inst, attr, value): + if not bool(value): + raise ValueError( + "'{name}' must be truthy (got {value!r})".format( + name=attr.name, value=value + ) + ) + + +def check_is_instance(name, value, clazz): + if not isinstance(value, clazz): + raise TypeError( + "'{name}' must be {type!r} (got {value!r} that is a " + "{actual!r}).".format( + name=name, + type=clazz, + actual=value.__class__, + value=value, + ), + name, + clazz, + value, + ) + + +def validate_utc(inst, attr, value): + check_is_instance(attr.name, value, datetime) + if not is_utc(value): + raise ValueError( + "'{name}' must be in timezone UTC (got {value!r} which has tzinfo {tzinfo!r})".format( + name=attr.name, value=value, tzinfo=value.tzinfo + ) + ) + + +def call_validate_on_inst(inst, attr, value): + inst.validate(attr, value) diff --git a/src/ics/valuetype/__init__.py b/src/ics/valuetype/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/ics/valuetype/base.py b/src/ics/valuetype/base.py new file mode 100644 index 00000000..7ac354a7 --- /dev/null +++ b/src/ics/valuetype/base.py @@ -0,0 +1,51 @@ +import abc +import inspect +from typing import Dict, Generic, Iterable, Type, TypeVar + +from ics.types import ContextDict, EmptyContext, EmptyParams, ExtraParams + +T = TypeVar('T') + + +class ValueConverter(Generic[T], abc.ABC): + BY_NAME: Dict[str, "ValueConverter"] = {} + BY_TYPE: Dict[Type, "ValueConverter"] = {} + INST: "ValueConverter" + + def __init_subclass__(cls) -> None: + super(ValueConverter, cls).__init_subclass__() + # isabstract(ValueConverter) == False on python 3.6 + if not inspect.isabstract(cls) and cls.parse is not ValueConverter.parse: + cls.INST = cls() + ValueConverter.BY_NAME[cls.INST.ics_type] = cls.INST + ValueConverter.BY_TYPE.setdefault(cls.INST.python_type, cls.INST) + + @property + @abc.abstractmethod + def ics_type(self) -> str: + ... + + @property + @abc.abstractmethod + def python_type(self) -> Type[T]: + ... + + def split_value_list(self, values: str) -> Iterable[str]: + yield from values.split(",") + + def join_value_list(self, values: Iterable[str]) -> str: + return ",".join(values) + + @abc.abstractmethod + def parse(self, value: str, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> T: + ... + + @abc.abstractmethod + def serialize(self, value: T, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> str: + ... + + def __str__(self): + return "<" + self.__class__.__name__ + ">" + + def __hash__(self): + return hash(type(self)) diff --git a/src/ics/valuetype/datetime.py b/src/ics/valuetype/datetime.py new file mode 100644 index 00000000..c49dedd1 --- /dev/null +++ b/src/ics/valuetype/datetime.py @@ -0,0 +1,294 @@ +import re +import warnings +from datetime import date, datetime, time, timedelta +from typing import List, Optional, Type, cast + +from dateutil.tz import UTC as dateutil_tzutc, gettz, tzoffset as UTCOffset + +from ics.timespan import Timespan +from ics.types import ContextDict, EmptyContext, EmptyParams, ExtraParams, copy_extra_params +from ics.utils import is_utc +from ics.valuetype.base import ValueConverter + + +class DatetimeConverterMixin(object): + FORMATS = { + 6: "%Y%m", + 8: "%Y%m%d" + } + CONTEXT_KEY_AVAILABLE_TZ = "DatetimeAvailableTimezones" + + def _serialize_dt(self, value: datetime, params: ExtraParams, context: ContextDict, + utc_fmt="%Y%m%dT%H%M%SZ", nonutc_fmt="%Y%m%dT%H%M%S") -> str: + if is_utc(value): + return value.strftime(utc_fmt) + else: + if value.tzinfo is not None: + tzname = value.tzinfo.tzname(value) + if not tzname: + # TODO generate unique identifier as name + raise ValueError("could not generate name for tzinfo %s" % value.tzinfo) + params["TZID"] = [tzname] + available_tz = context.setdefault(self.CONTEXT_KEY_AVAILABLE_TZ, {}) + available_tz.setdefault(tzname, value.tzinfo) + return value.strftime(nonutc_fmt) + + def _parse_dt(self, value: str, params: ExtraParams, context: ContextDict, + warn_no_avail_tz=True) -> datetime: + param_tz_list: Optional[List[str]] = params.pop("TZID", None) # we remove the TZID from context + if param_tz_list: + if len(param_tz_list) > 1: + raise ValueError("got multiple TZIDs") + param_tz: Optional[str] = param_tz_list[0] + else: + param_tz = None + available_tz = context.get(self.CONTEXT_KEY_AVAILABLE_TZ, None) + if available_tz is None and warn_no_avail_tz: + warnings.warn("DatetimeConverterMixin.parse called without available_tz dict in context") + fixed_utc = (value[-1].upper() == 'Z') + + value = value.translate({ + ord("/"): "", + ord("-"): "", + ord("Z"): "", + ord("z"): ""}) + dt = datetime.strptime(value, self.FORMATS[len(value)]) + + if fixed_utc: + if param_tz: + raise ValueError("can't specify UTC via appended 'Z' and TZID param '%s'" % param_tz) + return dt.replace(tzinfo=dateutil_tzutc) + elif param_tz: + selected_tz = None + if available_tz: + selected_tz = available_tz.get(param_tz, None) + if selected_tz is None: + selected_tz = gettz(param_tz) # be lenient with missing vtimezone definitions + return dt.replace(tzinfo=selected_tz) + else: + return dt + + +class DatetimeConverter(DatetimeConverterMixin, ValueConverter[datetime]): + FORMATS = { + **DatetimeConverterMixin.FORMATS, + 11: "%Y%m%dT%H", + 13: "%Y%m%dT%H%M", + 15: "%Y%m%dT%H%M%S" + } + + @property + def ics_type(self) -> str: + return "DATE-TIME" + + @property + def python_type(self) -> Type[datetime]: + return datetime + + def serialize(self, value: datetime, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> str: + return self._serialize_dt(value, params, context) + + def parse(self, value: str, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> datetime: + return self._parse_dt(value, params, context) + + +class DateConverter(DatetimeConverterMixin, ValueConverter[date]): + @property + def ics_type(self) -> str: + return "DATE" + + @property + def python_type(self) -> Type[date]: + return date + + def serialize(self, value, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext): + return value.strftime("%Y%m%d") + + def parse(self, value, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext): + return self._parse_dt(value, params, context, warn_no_avail_tz=False).date() + + +class TimeConverter(DatetimeConverterMixin, ValueConverter[time]): + FORMATS = { + 2: "%H", + 4: "%H%M", + 6: "%H%M%S" + } + + @property + def ics_type(self) -> str: + return "TIME" + + @property + def python_type(self) -> Type[time]: + return time + + def serialize(self, value, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext): + return self._serialize_dt(value, params, context, utc_fmt="%H%M%SZ", nonutc_fmt="%H%M%S") + + def parse(self, value, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext): + return self._parse_dt(value, params, context).timetz() + + +class UTCOffsetConverter(ValueConverter[UTCOffset]): + @property + def ics_type(self) -> str: + return "UTC-OFFSET" + + @property + def python_type(self) -> Type[UTCOffset]: + return UTCOffset + + def parse(self, value: str, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> UTCOffset: + match = re.fullmatch(r"(?P\+|-|)(?P[0-9]{2})(?P[0-9]{2})(?P[0-9]{2})?", value) + if not match: + raise ValueError("value '%s' is not a valid UTCOffset") + groups = match.groupdict() + sign = groups.pop("sign") + td = timedelta(**{k: int(v) for k, v in groups.items() if v}) + if sign == "-": + td *= -1 + return UTCOffset(value, td) + + def serialize(self, value: UTCOffset, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> str: + offset = value.utcoffset(None) + assert offset is not None + seconds = offset.seconds + if seconds < 0: + res = "-" + else: + res = "+" + + # hours + res += '%02d' % (seconds // 3600) + seconds %= 3600 + + # minutes + res += '%02d' % (seconds // 60) + seconds %= 60 + + if seconds: + # seconds + res += '%02d' % seconds + + return res + + +class DurationConverter(ValueConverter[timedelta]): + @property + def ics_type(self) -> str: + return "DURATION" + + @property + def python_type(self) -> Type[timedelta]: + return timedelta + + def parse(self, value: str, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> timedelta: + DAYS = {'D': 1, 'W': 7} + SECS = {'S': 1, 'M': 60, 'H': 3600} + + sign, i = 1, 0 + if value[i] in '-+': + if value[i] == '-': + sign = -1 + i += 1 + if value[i] != 'P': + raise ValueError("Error while parsing %s" % value) + i += 1 + days, secs = 0, 0 + while i < len(value): + if value[i] == 'T': + i += 1 + if i == len(value): + break + j = i + while value[j].isdigit(): + j += 1 + if i == j: + raise ValueError("Error while parsing %s" % value) + val = int(value[i:j]) + if value[j] in DAYS: + days += val * DAYS[value[j]] + DAYS.pop(value[j]) + elif value[j] in SECS: + secs += val * SECS[value[j]] + SECS.pop(value[j]) + else: + raise ValueError("Error while parsing %s" % value) + i = j + 1 + return timedelta(sign * days, sign * secs) + + def serialize(self, value: timedelta, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> str: + ONE_DAY_IN_SECS = 3600 * 24 + total = abs(int(value.total_seconds())) + days = total // ONE_DAY_IN_SECS + seconds = total % ONE_DAY_IN_SECS + + res = '' + if days: + res += str(days) + 'D' + if seconds: + res += 'T' + if seconds // 3600: + res += str(seconds // 3600) + 'H' + seconds %= 3600 + if seconds // 60: + res += str(seconds // 60) + 'M' + seconds %= 60 + if seconds: + res += str(seconds) + 'S' + + if not res: + res = 'T0S' + if value.total_seconds() >= 0: + return 'P' + res + else: + return '-P%s' % res + + +class PeriodConverter(DatetimeConverterMixin, ValueConverter[Timespan]): + + @property + def ics_type(self) -> str: + return "PERIOD" + + @property + def python_type(self) -> Type[Timespan]: + return Timespan + + def parse(self, value: str, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext): + start, sep, end = value.partition("/") + if not sep: + raise ValueError("PERIOD '%s' must contain the separator '/'") + if end.startswith("P"): # period-start = date-time "/" dur-value + return Timespan(begin_time=self._parse_dt(start, params, context), + duration=DurationConverter.INST.parse(end, params, context)) + else: # period-explicit = date-time "/" date-time + end_params = copy_extra_params(params) # ensure that the first parse doesn't remove TZID also needed by the second call + return Timespan(begin_time=self._parse_dt(start, params, context), + end_time=self._parse_dt(end, end_params, context)) + + def serialize(self, value: Timespan, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> str: + # note: there are no DATE to DATE / all-day periods + begin = value.get_begin() + if begin is None: + raise ValueError("PERIOD must have a begin timestamp") + if value.get_end_representation() == "duration": + duration = cast(timedelta, value.get_effective_duration()) + return "%s/%s" % ( + self._serialize_dt(begin, params, context), + DurationConverter.INST.serialize(duration, params, context) + ) + else: + end = value.get_effective_end() + if end is None: + raise ValueError("PERIOD must have a end timestamp") + end_params = copy_extra_params(params) + res = "%s/%s" % ( + self._serialize_dt(begin, params, context), + self._serialize_dt(end, end_params, context) + ) + if end_params != params: + raise ValueError("Begin and end time of PERIOD %s must serialize to the same params! " + "Got %s != %s." % (value, params, end_params)) + return res diff --git a/src/ics/valuetype/generic.py b/src/ics/valuetype/generic.py new file mode 100644 index 00000000..4c2cdc22 --- /dev/null +++ b/src/ics/valuetype/generic.py @@ -0,0 +1,144 @@ +import base64 +from typing import Type +from urllib.parse import urlparse + +from dateutil.rrule import rrule + +from ics.types import ContextDict, EmptyContext, EmptyParams, ExtraParams, URL +from ics.valuetype.base import ValueConverter + + +class BinaryConverter(ValueConverter[bytes]): + + @property + def ics_type(self) -> str: + return "BINARY" + + @property + def python_type(self) -> Type[bytes]: + return bytes + + def parse(self, value: str, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> bytes: + return base64.b64decode(value) + + def serialize(self, value: bytes, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> str: + return base64.b64encode(value).decode("ascii") + + +ValueConverter.BY_TYPE[bytearray] = ValueConverter.BY_TYPE[bytes] + + +class BooleanConverter(ValueConverter[bool]): + + @property + def ics_type(self) -> str: + return "BOOLEAN" + + @property + def python_type(self) -> Type[bool]: + return bool + + def parse(self, value: str, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> bool: + if value == "TRUE": + return True + elif value == "FALSE": + return False + else: + value = value.upper() + if value == "TRUE": + return True + elif value == "FALSE": + return False + elif value in ["T", "Y", "YES", "ON", "1"]: + return True + elif value in ["F", "N", "NO", "OFF", "0"]: + return False + else: + raise ValueError("can't interpret '%s' as boolen" % value) + + def serialize(self, value: bool, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> str: + if value: + return "TRUE" + else: + return "FALSE" + + +class IntegerConverter(ValueConverter[int]): + + @property + def ics_type(self) -> str: + return "INTEGER" + + @property + def python_type(self) -> Type[int]: + return int + + def parse(self, value: str, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> int: + return int(value) + + def serialize(self, value: int, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> str: + return str(value) + + +class FloatConverter(ValueConverter[float]): + + @property + def ics_type(self) -> str: + return "FLOAT" + + @property + def python_type(self) -> Type[float]: + return float + + def parse(self, value: str, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> float: + return float(value) + + def serialize(self, value: float, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> str: + return str(value) + + +class RecurConverter(ValueConverter[rrule]): + + @property + def ics_type(self) -> str: + return "RECUR" + + @property + def python_type(self) -> Type[rrule]: + return rrule + + def parse(self, value: str, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> rrule: + # this won't be called unless a class specifies an attribute with type: rrule + raise NotImplementedError("parsing 'RECUR' is not yet supported") # TODO is this a valuetype or a composed object + + def serialize(self, value: rrule, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> str: + raise NotImplementedError("serializing 'RECUR' is not yet supported") + + +class URIConverter(ValueConverter[URL]): + # TODO URI PARAMs need percent escaping, preventing all illegal characters except for ", in which they also need to wrapped + # TODO URI values also need percent escaping (escaping COMMA characters in URI Lists), but no quoting + + @property + def ics_type(self) -> str: + return "URI" + + @property + def python_type(self) -> Type[URL]: + return URL + + def parse(self, value: str, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> URL: + return urlparse(value) + + def serialize(self, value: URL, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> str: + if isinstance(value, str): + return value + else: + return value.geturl() + + +class CalendarUserAddressConverter(URIConverter): + + @property + def ics_type(self) -> str: + return "CAL-ADDRESS" diff --git a/src/ics/valuetype/special.py b/src/ics/valuetype/special.py new file mode 100644 index 00000000..60698a59 --- /dev/null +++ b/src/ics/valuetype/special.py @@ -0,0 +1,25 @@ +from typing import Type + +from ics.geo import Geo +from ics.types import ContextDict, EmptyContext, EmptyParams, ExtraParams +from ics.valuetype.base import ValueConverter + + +class GeoConverter(ValueConverter[Geo]): + + @property + def ics_type(self) -> str: + return "X-GEO" + + @property + def python_type(self) -> Type[Geo]: + return Geo + + def parse(self, value: str, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> Geo: + latitude, sep, longitude = value.partition(";") + if not sep: + raise ValueError("geo must have two float values") + return Geo(float(latitude), float(longitude)) + + def serialize(self, value: Geo, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> str: + return "%f;%f" % value diff --git a/src/ics/valuetype/text.py b/src/ics/valuetype/text.py new file mode 100644 index 00000000..0a172972 --- /dev/null +++ b/src/ics/valuetype/text.py @@ -0,0 +1,68 @@ +from typing import Iterable, Iterator, Type + +from ics.types import ContextDict, EmptyContext, EmptyParams, ExtraParams +from ics.utils import next_after_str_escape +from ics.valuetype.base import ValueConverter + + +class TextConverter(ValueConverter[str]): + + @property + def ics_type(self) -> str: + return "TEXT" + + @property + def python_type(self) -> Type[str]: + return str + + def parse(self, value: str, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> str: + return self.unescape_text(value) + + def serialize(self, value: str, params: ExtraParams = EmptyParams, context: ContextDict = EmptyContext) -> str: + return self.escape_text(value) + + def split_value_list(self, values: str) -> Iterable[str]: + it = iter(values.split(",")) + for val in it: + while val.endswith("\\") and not val.endswith("\\\\"): + val += "," + next_after_str_escape(it, full_str=values) + yield val + + # def join_value_list(self, values: Iterable[str]) -> str: + # return ",".join(values) # TODO warn about missing escapes + + @classmethod + def escape_text(cls, string: str) -> str: + return string.translate( + {ord("\\"): "\\\\", + ord(";"): "\\;", + ord(","): "\\,", + ord("\n"): "\\n", + ord("\r"): "\\r"}) + + @classmethod + def unescape_text(cls, string: str) -> str: + return "".join(cls.unescape_text_iter(string)) + + @classmethod + def unescape_text_iter(cls, string: str) -> Iterator[str]: + it = iter(string) + for c1 in it: + if c1 == "\\": + c2 = next_after_str_escape(it, full_str=string) + if c2 == ";": + yield ";" + elif c2 == ",": + yield "," + elif c2 == "n" or c2 == "N": + yield "\n" + elif c2 == "r" or c2 == "R": + yield "\r" + elif c2 == "\\": + yield "\\" + else: + raise ValueError("can't handle escaped character '%s'" % c2) + elif c1 in ";,\n\r": + raise ValueError("unescaped character '%s' in TEXT value" % c1) + else: + yield c1 diff --git a/tests/__init__.py b/tests/__init__.py index e69de29b..b0433967 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,18 @@ +import sys + +import pkg_resources + +import ics + + +def test_version_matches(): + dist = pkg_resources.get_distribution('ics') + print(repr(dist), dist.__dict__, sys.path, ics.__path__) + assert len(ics.__path__) == 1 + ics_path = ics.__path__[0] + assert "/site-packages/" in ics_path and not "/src" in ics_path, \ + "ics should be imported from package not from sources '%s' for testing" % ics_path + for path in sys.path: + assert not path.endswith("/src"), \ + "Project sources should not be in PYTHONPATH when testing, conflicting entry: %s" % path + assert pkg_resources.parse_version(ics.__version__) == dist.parsed_version diff --git a/tests/alarm.py b/tests/alarm.py deleted file mode 100644 index 1263cb2b..00000000 --- a/tests/alarm.py +++ /dev/null @@ -1,229 +0,0 @@ -import unittest -from datetime import datetime, timedelta - -import attr -from dateutil.tz import UTC as tzutc - -from ics.alarm import AudioAlarm, DisplayAlarm -from ics.alarm.base import BaseAlarm -from ics.alarm.custom import CustomAlarm -from ics.alarm.email import EmailAlarm -from ics.alarm.none import NoneAlarm -from ics.grammar.parse import ContentLine -from ics.icalendar import Calendar -from .fixture import cal21, cal22, cal23, cal24, cal25, cal35, cal36 - -CRLF = "\r\n" - - -class FakeAlarm(BaseAlarm): - @property - def action(self): - return "FAKE" - - -class TestAlarm(unittest.TestCase): - def test_alarm_timedelta_trigger(self): - a = FakeAlarm(trigger=timedelta(minutes=15)) - self.assertEqual(15 * 60, a.trigger.total_seconds()) - - def test_alarm_datetime_trigger(self): - alarm_time = datetime(year=2016, month=1, day=1, hour=0, minute=0, second=0) - a = FakeAlarm(trigger=alarm_time) - self.assertEqual(alarm_time, a.trigger) - - def test_alarm_repeat(self): - a = FakeAlarm( - trigger=timedelta(minutes=15), repeat=2, duration=timedelta(minutes=10) - ) - self.assertEqual(15 * 60, a.trigger.total_seconds()) - self.assertEqual(2, a.repeat) - self.assertEqual(10 * 60, a.duration.total_seconds()) - - def test_alarm_invalid_repeat(self): - with self.assertRaises(ValueError): - FakeAlarm( - trigger=timedelta(minutes=15), repeat=-2, duration=timedelta(minutes=10) - ) - - def test_alarm_invalid_duration(self): - with self.assertRaises(ValueError): - FakeAlarm( - trigger=timedelta(minutes=15), repeat=2, duration=timedelta(minutes=-10) - ) - - def test_alarm_missing_duration(self): - with self.assertRaises(ValueError): - FakeAlarm(trigger=timedelta(minutes=15), repeat=2) - - def test_alarm_timedelta_trigger_output(self): - a = FakeAlarm(trigger=timedelta(minutes=15)) - - desired_output = CRLF.join( - ["BEGIN:VALARM", "ACTION:FAKE", "TRIGGER:PT15M", "END:VALARM"] - ) - - self.assertEqual(desired_output, str(a)) - - def test_alarm_datetime_trigger_output(self): - alarm_time = datetime(year=2016, month=1, day=1, hour=0, minute=0, second=0, tzinfo=tzutc) - a = FakeAlarm(trigger=alarm_time) - - desired_output = CRLF.join( - [ - "BEGIN:VALARM", - "ACTION:FAKE", - "TRIGGER;VALUE=DATE-TIME:20160101T000000Z", - "END:VALARM", - ] - ) - - self.assertEqual(desired_output, str(a)) - - def test_alarm_repeat_duration_output(self): - a = FakeAlarm( - trigger=timedelta(minutes=15), repeat=2, duration=timedelta(minutes=10) - ) - - desired_output = CRLF.join( - [ - "BEGIN:VALARM", - "ACTION:FAKE", - "DURATION:PT10M", - "REPEAT:2", - "TRIGGER:PT15M", - "END:VALARM", - ] - ) - - self.assertEqual(desired_output, str(a)) - - -class TestDisplayAlarm(unittest.TestCase): - def test_alarm(self): - test_description = "Test description" - a = DisplayAlarm(trigger=timedelta(minutes=15), display_text=test_description) - self.assertEqual(test_description, a.display_text) - - def test_alarm_output(self): - a = DisplayAlarm(trigger=timedelta(minutes=15), display_text="Test description") - - desired_output = CRLF.join( - [ - "BEGIN:VALARM", - "ACTION:DISPLAY", - "DESCRIPTION:Test description", - "TRIGGER:PT15M", - "END:VALARM", - ] - ) - - self.assertEqual(desired_output, str(a)) - - def test_alarm_without_repeat_extraction(self): - c = Calendar(cal21) - e = next(iter(c.events)) - assert isinstance(e.alarms, list) - a = e.alarms[0] - self.assertEqual(a.trigger, timedelta(hours=1)) - self.assertIsNone(a.repeat) - self.assertIsNone(a.duration) - self.assertEqual(a.display_text, "Event reminder") - - def test_alarm_with_repeat_extraction(self): - c = Calendar(cal22) - a = next(iter(c.events)).alarms[0] - self.assertEqual(a.trigger, timedelta(hours=1)) - self.assertEqual(a.repeat, 2) - self.assertEqual(a.duration, timedelta(minutes=10)) - self.assertEqual(a.display_text, "Event reminder") - - def test_alarm_without_repeat_datetime_trigger_extraction(self): - c = Calendar(cal23) - a = next(iter(c.events)).alarms[0] - - alarm_time = datetime(year=2016, month=1, day=1, hour=0, minute=0, second=0, tzinfo=tzutc) - self.assertEqual(a.trigger, alarm_time) - self.assertIsNone(a.repeat) - self.assertIsNone(a.duration) - self.assertEqual(a.display_text, "Event reminder") - - -class TestAudioAlarm(unittest.TestCase): - def test_alarm(self): - a = AudioAlarm(trigger=timedelta(minutes=15)) - self.assertEqual("AUDIO", a.action) - - def test_plain_repr(self): - a = AudioAlarm(trigger=timedelta(minutes=15)) - self.assertEqual(repr(a), "") - - def test_alarm_output(self): - attach = "ftp://example.com/pub/sounds/bell-01.aud" - attach_params = {"FMTTYPE": ["audio/basic"]} - a = AudioAlarm(trigger=timedelta(minutes=15)) - a.sound = ContentLine("ATTACH", value=attach, params=attach_params) - - desired_output = CRLF.join( - [ - "BEGIN:VALARM", - "ACTION:AUDIO", - "ATTACH;FMTTYPE=audio/basic:{0}".format(attach), - "TRIGGER:PT15M", - "END:VALARM", - ] - ) - - self.assertEqual(desired_output, str(a)) - - def test_alarm_without_attach_extraction(self): - c = Calendar(cal24) - a = next(iter(c.events)).alarms[0] - alarm_time = datetime(year=2016, month=1, day=1, hour=0, minute=0, second=0, tzinfo=tzutc) - - self.assertEqual(a.action, "AUDIO") - self.assertEqual(a.trigger, alarm_time) - self.assertIsNone(a.repeat) - self.assertIsNone(a.duration) - self.assertIsNone(a.sound) - - def test_alarm_with_attach_extraction(self): - c = Calendar(cal25) - a = next(iter(c.events)).alarms[0] - alarm_time = datetime(year=2016, month=1, day=1, hour=0, minute=0, second=0, tzinfo=tzutc) - - self.assertEqual(a.action, "AUDIO") - self.assertEqual(a.trigger, alarm_time) - self.assertIsNone(a.repeat) - self.assertIsNone(a.duration) - self.assertEqual(a.sound.value, "ftp://example.com/pub/sounds/bell-01.aud") - self.assertIn("FMTTYPE", a.sound.params.keys()) - self.assertEqual(1, len(a.sound.params["FMTTYPE"])) - self.assertEqual("audio/basic", a.sound.params["FMTTYPE"][0]) - - -def test_none(): - c = Calendar(cal35) - a = next(iter(c.events)).alarms[0] - assert isinstance(a, NoneAlarm) - - -def test_custom(): - c = Calendar(cal36) - a = next(iter(c.events)).alarms[0] - assert isinstance(a, CustomAlarm) - assert a.action == "YOLO" - - -def test_custom_back_forth(): - c = Calendar(cal36) - c1 = Calendar(str(c)) - c.events[0].dtstamp = c1.events[0].dtstamp = datetime.now() - assert attr.asdict(c) == attr.asdict(c1) - assert c == c1 - - -class TestEmailAlarm(unittest.TestCase): - def test_alarm(self): - a = EmailAlarm(trigger=timedelta(minutes=15)) - self.assertEqual("EMAIL", a.action) diff --git a/tests/calendar.py b/tests/calendar.py deleted file mode 100644 index 2af5719a..00000000 --- a/tests/calendar.py +++ /dev/null @@ -1,233 +0,0 @@ -import unittest -from collections.abc import Iterable -from datetime import datetime - -import attr -from dateutil.tz import UTC as tzutc, gettz - -from ics.event import Event -from ics.grammar.parse import Container -from ics.icalendar import Calendar -from ics.todo import Todo -from .fixture import cal1, cal10, cal12, cal14, cal34 - - -class TestCalendar(unittest.TestCase): - fixtures = [cal1, cal10, cal12] - - def test_init(self): - c = Calendar(creator='tests') - self.assertEqual(c.creator, 'tests') - self.assertSequenceEqual(c.events, []) - self.assertSequenceEqual(c.todos, []) - self.assertEqual(c.method, None) - self.assertEqual(c.scale, None) - self.assertEqual(c.extra, Container(name='VCALENDAR')) - self.assertEqual(c._timezones, {}) - - def test_selfload(self): - def filter(attr, value): - return not attr.name.startswith("_classmethod") and not attr.name == "_timezones" - - for fix in self.fixtures: - c = Calendar(fix) - d = Calendar(str(c)) - self.assertEqual(attr.asdict(c, filter=filter), attr.asdict(d, filter=filter)) - self.assertEqual(c, d) - self.assertEqual(c.events, d.events) - self.assertEqual(c.todos, d.todos) - - e = Calendar(str(d)) - # cannot compare str(c) and str(d) because times are encoded differently - self.assertEqual(str(d), str(e)) - - def test_repr(self): - c = Calendar() - self.assertEqual(c.__repr__(), '') - - c.events.append(Event()) - c.todos.append(Todo()) - self.assertEqual(c.__repr__(), '') - - c.events.append(Event()) - c.todos.append(Todo()) - self.assertEqual(c.__repr__(), '') - - def test_iter(self): - for fix in self.fixtures: - c = Calendar(imports=fix) - s = str(c) - self.assertIsInstance(c, Iterable) - i_with_no_lr = map(lambda x: x.rstrip('\n'), c) - self.assertSequenceEqual(s.split('\n'), list(i_with_no_lr)) - - def test_eq(self): - c0, c1 = Calendar(), Calendar() - e = Event() - - c0.events.append(e) - c1.events.append(e) - - self.assertEqual(c0, c1) - - t = Todo() - - c0.todos.append(t) - c1.todos.append(t) - - def test_neq_len(self): - c0, c1 = Calendar(), Calendar() - e1 = Event() - e2 = Event() - - c0.events.append(e1) - c0.events.append(e2) - - c1.events.append(e1) - - self.assertNotEqual(c0, c1) - - t1 = Todo() - t2 = Todo() - - c0.todos.append(t1) - c0.todos.append(t2) - - c1.todos.append(t1) - - self.assertNotEqual(c0, c1) - - def test_eq_len(self): - c0, c1 = Calendar(), Calendar() - e = Event() - - c0.events.append(e) - c1.events.append(e) - - self.assertEqual(c0, c1) - - t = Todo() - - c0.todos.append(t) - c1.todos.append(t) - - self.assertEqual(c0, c1) - - def test_neq_events(self): - c0, c1 = Calendar(), Calendar() - e0, e1 = Event(), Event() - - c0.events.append(e0) - c1.events.append(e1) - - self.assertNotEqual(c0, c1) - - def test_neq_todos(self): - c0, c1 = Calendar(), Calendar() - t0, t1 = Todo(), Todo() - - c0.events.append(t0) - c1.events.append(t1) - - self.assertNotEqual(c0, c1) - - def test_neq_creator(self): - c0, c1 = Calendar(), Calendar(creator="test") - self.assertNotEqual(c0, c1) - - def test_creator(self): - - c0 = Calendar() - c1 = Calendar() - c0.creator = u'42' - with self.assertRaises(TypeError): - c1.creator = 42 - - self.assertEqual(c0.creator, u'42') - - def test_existing_creator(self): - c = Calendar(cal1) - self.assertEqual(c.creator, u'-//Apple Inc.//Mac OS X 10.9//EN') - - c.creator = u"apple_is_a_fruit" - self.assertEqual(c.creator, u"apple_is_a_fruit") - - def test_scale(self): - - c = Calendar(cal10) - - self.assertEqual(c.scale, u'georgian') - - def test_version(self): - - c = Calendar(cal10) - self.assertEqual(u'2.0', c.version) - lines = str(c).splitlines() - self.assertEqual(lines[:3], cal10.strip().splitlines()[:3]) - self.assertEqual("VERSION:2.0", lines[1]) - self.assertIn("PRODID", lines[2]) - - c = Calendar(cal14) - self.assertEqual(u'42', c.version) - - def test_events_setter(self): - - c = Calendar(cal1) - e = Event() - c.events = [e] - - self.assertEqual(c.events, [e]) - - def test_todos_setter(self): - - c = Calendar(cal1) - t = Todo() - c.todos = [t] - - self.assertEqual(c.todos, [t]) - - def test_clone(self): - c0 = Calendar() - e = Event() - t = Todo() - c0.events.append(e) - c0.todos.append(t) - c1 = c0.clone() - - self.assertEqual(c0.events, c1.events) - self.assertEqual(c0.todos, c1.todos) - self.assertEqual(c0, c1) - - def test_multiple_calendars(self): - - with self.assertRaises(TypeError): - Calendar() + Calendar() - - def test_init_int(self): - - with self.assertRaises(TypeError): - Calendar(42) - - def test_imports(self): - c = Calendar(cal1) - self.assertEqual(c.creator, '-//Apple Inc.//Mac OS X 10.9//EN') - self.assertEqual(c.method, 'PUBLISH') - e = next(iter(c.events)) - self.assertFalse(e.all_day) - tz = gettz('Europe/Brussels') - self.assertEqual(datetime(2013, 10, 29, 10, 30, tzinfo=tz), e.begin) - self.assertEqual(datetime(2013, 10, 29, 11, 30, tzinfo=tz), e.end) - self.assertEqual(1, len(c.events)) - t = next(iter(c.todos)) - self.assertEqual(datetime(2018, 2, 18, 15, 47, tzinfo=tzutc), t.dtstamp) - self.assertEqual('Uid', t.uid) - self.assertEqual(1, len(c.todos)) - - def test_multiple(self): - cals = Calendar.parse_multiple(cal34) - self.assertEqual(len(cals), 2) - - e1 = list(cals[0].events)[0] - self.assertEqual(e1.name, "a") - e2 = list(cals[1].events)[0] - self.assertEqual(e2.name, "b") diff --git a/tests/component.py b/tests/component.py deleted file mode 100644 index 8308ae21..00000000 --- a/tests/component.py +++ /dev/null @@ -1,235 +0,0 @@ -import copy -import unittest - -from ics.component import Component -from ics.icalendar import Calendar -from ics.grammar.parse import Container, ContentLine -from ics.parsers.parser import Parser, option -from ics.serializers.serializer import Serializer - -from .fixture import cal2 - -fix1 = "BEGIN:BASETEST\r\nATTR:FOOBAR\r\nEND:BASETEST" - -fix2 = "BEGIN:BASETEST\r\nATTR:FOO\r\nATTR2:BAR\r\nEND:BASETEST" - - -class TestComponent(unittest.TestCase): - - def test_valueerror(self): - - with self.assertRaises(ValueError): - Calendar(cal2) - - def test_bad_type(self): - container = Container(name='VINVALID') - with self.assertRaises(ValueError): - Calendar._from_container(container) - - def test_base(self): - assert CT4.Meta.name == "TEST" - e = CT4.Meta.parser.get_parsers() - assert len(e) == 1 - - def test_parser(self): - c = CT1() - c.some_attr = "foobar" - expected = fix1 - self.assertEqual(str(c), expected) - - def test_2parsers(self): - c = CT2() - c.some_attr = "foo" - c.some_attr2 = "bar" - expected = fix2 - self.assertEqual(str(c), expected) - - def test_empty_input(self): - cont = Container("TEST") - c = CT1._from_container(cont) - self.assertEqual(c.extra.name, "TEST") - self.assertEqual(c.some_attr, "biiip") - - def test_no_match_input(self): - cont = Container("TEST") - cont.append(ContentLine(name="NOMATCH", value="anything")) - cont2 = copy.deepcopy(cont) - - c = CT1._from_container(cont) - self.assertEqual(c.extra.name, "TEST") - self.assertEqual(c.some_attr, "biiip") - self.assertEqual(cont2, c.extra) - - def test_input(self): - cont = Container("TEST") - cont.append(ContentLine(name="ATTR", value="anything")) - - c = CT1._from_container(cont) - self.assertEqual(c.extra.name, "TEST") - self.assertEqual(c.some_attr, "anything") - self.assertEqual(Container("TEST"), c.extra) - - def test_input_plus_extra(self): - cont = Container("TEST") - cont.append(ContentLine(name="ATTR", value="anything")) - cont.append(ContentLine(name="PLOP", value="plip")) - - unused = Container("TEST") - unused.append(ContentLine(name="PLOP", value="plip")) - - c = CT1._from_container(cont) - self.assertEqual(c.extra.name, "TEST") - self.assertEqual(c.some_attr, "anything") - self.assertEqual(unused, c.extra) - - def test_required_raises(self): - cont = Container("TEST") - cont.append(ContentLine(name="PLOP", value="plip")) - - with self.assertRaises(ValueError): - CT2._from_container(cont) - - def test_required(self): - cont = Container("TEST") - cont.append(ContentLine(name="ATTR", value="anything")) - cont.append(ContentLine(name="PLOP", value="plip")) - - unused = Container("TEST") - unused.append(ContentLine(name="PLOP", value="plip")) - - c = CT2._from_container(cont) - self.assertEqual(c.some_attr, "anything") - self.assertEqual(unused, c.extra) - - def test_multiple_non_allowed(self): - cont = Container("TEST") - cont.append(ContentLine(name="ATTR", value="anything")) - cont.append(ContentLine(name="ATTR", value="plip")) - - with self.assertRaises(ValueError): - CT1._from_container(cont) - - def test_multiple(self): - cont = Container("TEST") - cont.append(ContentLine(name="ATTR", value="anything")) - cont.append(ContentLine(name="ATTR", value="plip")) - - c = CT3._from_container(cont) - - self.assertEqual(c.some_attr, "plip, anything") - self.assertEqual(Container("TEST"), c.extra) - - def test_multiple_fail(self): - cont = Container("TEST") - - c = CT3._from_container(cont) - - self.assertEqual(c.some_attr, "biiip") - self.assertEqual(Container("TEST"), c.extra) - - def test_multiple_unique(self): - cont = Container("TEST") - cont.append(ContentLine(name="ATTR", value="anything")) - - c = CT3._from_container(cont) - - self.assertEqual(c.some_attr, "anything") - self.assertEqual(Container("TEST"), c.extra) - - def test_multiple_unique_required(self): - cont = Container("TEST") - cont.append(ContentLine(name="OTHER", value="anything")) - - with self.assertRaises(ValueError): - CT4._from_container(cont) - - -class ComponentBaseTest(Component): - class Meta: - name = "TEST" - - def __init__(self): - self.some_attr = "biiip" - self.some_attr2 = "baaaaaaaaaaap" - self.extra = Container('BASETEST') - - -# 1 -class CT1Parser(Parser): - def parse_attr(test, line): - if line: - test.some_attr = line.value - - -class CT1Serializer(Serializer): - def serialize_some_attr(test, container): - if test.some_attr: - container.append(ContentLine('ATTR', value=test.some_attr.upper())) - - -class CT1(ComponentBaseTest): - class Meta: - name = "TEST" - parser = CT1Parser - serializer = CT1Serializer - - -# 2 -class CT2Parser(Parser): - @option(required=True) - def parse_attr(test, line): - test.some_attr = line.value - - -class CT2Serializer(Serializer): - def serialize_some_attr2(test, container): - if test.some_attr: - container.append(ContentLine('ATTR', value=test.some_attr.upper())) - - def serialize_some_attr2bis(test, container): - if test.some_attr2: - container.append(ContentLine('ATTR2', value=test.some_attr2.upper())) - - -class CT2(ComponentBaseTest): - class Meta: - name = "TEST" - parser = CT2Parser - serializer = CT2Serializer - - -# 3 -class CT3Parser(Parser): - @option(multiple=True) - def parse_attr(test, line_list): - if line_list: - test.some_attr = ", ".join(map(lambda x: x.value, line_list)) - - -class CT3Serializer(Serializer): - pass - - -class CT3(ComponentBaseTest): - class Meta: - name = "TEST" - parser = CT3Parser - serializer = CT3Serializer - - -# 4 -class CT4Parser(Parser): - @option(required=True, multiple=True) - def parse_attr(test, line_list): - test.some_attr = ", ".join(map(lambda x: x.value, line_list)) - - -class CT4Serializer(Serializer): - pass - - -class CT4(ComponentBaseTest): - class Meta: - name = "TEST" - parser = CT4Parser - serializer = CT4Serializer diff --git a/tests/contentline.py b/tests/contentline.py deleted file mode 100644 index 82d0a507..00000000 --- a/tests/contentline.py +++ /dev/null @@ -1,83 +0,0 @@ -import unittest - -from ics.grammar.parse import ContentLine, ParseError -from ics.utils import parse_datetime - - -class TestContentLine(unittest.TestCase): - - dataset = { - 'HAHA:': ContentLine('haha'), - 'HAHA:hoho': ContentLine('haha', {}, 'hoho'), - 'HAHA:hoho:hihi': ContentLine('haha', {}, 'hoho:hihi'), - 'HAHA;hoho=1:hoho': ContentLine('haha', {'hoho': ['1']}, 'hoho'), - 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU': - ContentLine( - 'RRULE', - {}, - 'FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU' - ), - 'SUMMARY:dfqsdfjqkshflqsjdfhqs fqsfhlqs dfkqsldfkqsdfqsfqsfqsfs': - ContentLine( - 'SUMMARY', - {}, - 'dfqsdfjqkshflqsjdfhqs fqsfhlqs dfkqsldfkqsdfqsfqsfqsfs' - ), - 'DTSTART;TZID=Europe/Brussels:20131029T103000': - ContentLine( - 'DTSTART', - {'TZID': ['Europe/Brussels']}, - '20131029T103000' - ), - } - - dataset2 = { - 'haha;p2=v2;p1=v1:': - ContentLine( - 'haha', - {'p1': ['v1'], 'p2': ['v2']}, - '' - ), - 'haha;hihi=p3,p4,p5;hoho=p1,p2:blabla:blublu': - ContentLine( - 'haha', - {'hoho': ['p1', 'p2'], 'hihi': ['p3', 'p4', 'p5']}, - 'blabla:blublu' - ), - r'ATTENDEE;X-A="I&rsquo\;ll be in NYC":mailto:a@a.com': - ContentLine( - 'ATTENDEE', - {'X-A': [r"I&rsquo\;ll be in NYC"]}, - 'mailto:a@a.com', - ), - 'DTEND;TZID="UTC":20190107T000000': - ContentLine( - "DTEND", - {'TZID': ['UTC']}, - "20190107T000000" - ) - } - - def test_errors(self): - self.assertRaises(ParseError, ContentLine.parse, 'haha;p1=v1') - self.assertRaises(ParseError, ContentLine.parse, 'haha;p1:') - - def test_str(self): - for test in self.dataset: - expected = test - got = str(self.dataset[test]) - self.assertEqual(expected, got) - - def test_parse(self): - self.dataset2.update(self.dataset) - for test in self.dataset2: - expected = self.dataset2[test] - got = ContentLine.parse(test) - self.assertEqual(expected, got) - - - # https://github.com/C4ptainCrunch/ics.py/issues/68 - def test_timezone_not_dropped(self): - line = ContentLine.parse("DTSTART;TZID=Europe/Berlin:20151104T190000") - parsed = parse_datetime(line) - self.assertIn("Europe/Berlin", str(parsed.tzinfo)) diff --git a/tests/duration.py b/tests/duration.py deleted file mode 100644 index 9b41fd55..00000000 --- a/tests/duration.py +++ /dev/null @@ -1,17 +0,0 @@ -from ics.utils import parse_duration -from datetime import timedelta - - -def test_simple(): - s = "PT30M" - assert parse_duration(s) == timedelta(minutes=30) - - -def test_negative(): - s = "-PT30M" - assert parse_duration(s) == timedelta(minutes=-30) - - -def test_no_sign(): - s = "P0DT9H0M0S" - assert parse_duration(s) == timedelta(hours=9) diff --git a/tests/event.py b/tests/event.py deleted file mode 100644 index 8c131ea6..00000000 --- a/tests/event.py +++ /dev/null @@ -1,570 +0,0 @@ -import os -import unittest -from datetime import datetime as dt, timedelta as td - -import pytest -from dateutil.tz import UTC as tzutc - -from ics.attendee import Attendee, Organizer -from ics.event import Event -from ics.grammar.parse import Container -from ics.icalendar import Calendar -from .fixture import (cal12, cal13, cal15, cal16, cal17, cal18, cal19, - cal19bis, cal20, cal32, cal33_1, cal33_2, cal33_3, - cal33_4, cal33_5) - -CRLF = "\r\n" - -EVENT_A = Event(name="a") -EVENT_B = Event(name="b") -EVENT_M = Event(name="m") -EVENT_Z = Event(name="z") -EVENT_A.created = EVENT_B.created = EVENT_M.created = EVENT_Z.created = dt.now() - - -class TestEvent(unittest.TestCase): - - def test_event(self): - e = Event(begin=dt.fromtimestamp(0), end=dt.fromtimestamp(20)) - self.assertEqual(e.begin.timestamp(), 0) - self.assertEqual(e.end.timestamp(), 20) - self.assertTrue(e.has_explicit_end) - self.assertFalse(e.all_day) - - f = Event(begin=dt.fromtimestamp(10), end=dt.fromtimestamp(30)) - self.assertTrue(e < f) - self.assertTrue(e <= f) - self.assertTrue(f > e) - self.assertTrue(f >= e) - - def test_event_with_duration(self): - c = Calendar(cal12) - e = next(iter(c.events)) - self.assertEqual(e.duration, td(1, 3600)) - self.assertEqual(e.end - e.begin, td(1, 3600)) - - def test_event_with_geo(self): - c = Calendar(cal12) - e = list(c.events)[0] - self.assertEqual(e.geo, (40.779897, -73.968565)) - - def test_not_duration_and_end(self): - with self.assertRaises(ValueError): - Calendar(cal13) - - def test_duration_output(self): - e = Event(begin=dt.utcfromtimestamp(0), duration=td(1, 23)) - lines = str(e).splitlines() - self.assertIn('DTSTART:19700101T000000', lines) - self.assertIn('DURATION:P1DT23S', lines) - - def test_duration_output_utc(self): - e = Event(begin=dt.fromtimestamp(0, tz=tzutc), duration=td(days=8, hours=1, minutes=2, seconds=3)) - lines = str(e).splitlines() - self.assertIn('DTSTART:19700101T000000Z', lines) - self.assertIn('DURATION:P8DT1H2M3S', lines) - - def test_geo_output(self): - e = Event(geo=(40.779897, -73.968565)) - lines = str(e).splitlines() - self.assertIn('GEO:40.779897;-73.968565', lines) - - def test_init_duration_end(self): - with self.assertRaises(ValueError): - Event(name="plop", begin=dt.fromtimestamp(0), end=dt.fromtimestamp(10), duration=td(1)) - - def test_end_before_begin(self): - e = Event(begin=dt(2013, 10, 10)) - with self.assertRaises(ValueError): - e.end = dt(1999, 10, 10) - - def test_begin_after_end(self): - e = Event(begin=dt(1999, 10, 9), end=dt(1999, 10, 10)) - with self.assertRaises(ValueError): - e.begin = dt(2013, 10, 10) - - def test_plain_repr(self): - self.assertEqual("", repr(Event())) - - def test_all_day_repr(self): - e = Event(name='plop', begin=dt(1999, 10, 10)) - e.make_all_day() - self.assertEqual("", repr(e)) - self.assertEqual(dt(1999, 10, 11), e.end) - - def test_name_repr(self): - e = Event(name='plop') - self.assertEqual("", repr(e)) - - def test_repr(self): - e = Event(name='plop', begin=dt(1999, 10, 10)) - self.assertEqual("", repr(e)) - - def test_init(self): - e = Event() - - self.assertEqual(td(0), e.duration) - self.assertEqual(None, e.end) - self.assertEqual(None, e.begin) - self.assertEqual('second', e.timespan.precision) - self.assertNotEqual(None, e.uid) - self.assertEqual(None, e.description) - self.assertEqual(None, e.created) - self.assertEqual(None, e.last_modified) - self.assertEqual(None, e.location) - self.assertEqual(None, e.geo) - self.assertEqual(None, e.url) - self.assertEqual(Container(name='VEVENT'), e.extra) - self.assertEqual(None, e.status) - self.assertEqual(None, e.organizer) - - def test_has_explicit_end(self): - e = Event() - self.assertFalse(e.has_explicit_end) - e = Event(begin=dt(1993, 5, 24), duration=td(days=2)) - self.assertTrue(e.has_explicit_end) - e = Event(begin=dt(1993, 5, 24), end=dt(1999, 10, 11)) - self.assertTrue(e.has_explicit_end) - e = Event(begin=dt(1993, 5, 24)) - e.make_all_day() - self.assertFalse(e.has_explicit_end) - - def test_duration(self): - e = Event() - self.assertEqual(e.duration, td()) - - e1 = Event(begin=dt(1993, 5, 24)) - e1.make_all_day() - self.assertEqual(e1.duration, td(days=1)) - - e2 = Event(begin=dt(1993, 5, 24), end=dt(1993, 5, 30)) - self.assertEqual(e2.duration, td(days=6)) - - e3 = Event(begin=dt(1993, 5, 24), duration=td(minutes=1)) - self.assertEqual(e3.duration, td(minutes=1)) - - e4 = Event(begin=dt(1993, 5, 24)) - self.assertEqual(e4.duration, td(0)) - - e5 = Event(begin=dt(1993, 5, 24)) - e5.duration = {'days': 6, 'hours': 2} - self.assertEqual(e5.end, dt(1993, 5, 30, 2, 0)) - self.assertEqual(e5.duration, td(hours=146)) - - def test_geo(self): - e = Event() - self.assertIsNone(e.geo) - - e1 = Event(geo=(40.779897, -73.968565)) - self.assertEqual(e1.geo, (40.779897, -73.968565)) - - e2 = Event(geo={'latitude': 40.779897, 'longitude': -73.968565}) - self.assertEqual(e2.geo, (40.779897, -73.968565)) - - def test_attendee(self): - a = Attendee(email='email@email.com') - line = str(a) - self.assertIn("ATTENDEE;CN=email@email.com:mailto:email@email.com", line) - - a2 = Attendee(email='email@email.com', common_name='Email') - line = str(a2) - self.assertIn("ATTENDEE;CN=Email:mailto:email@email.com", line) - - a3 = Attendee( - email="email@email.com", - common_name="Email", - partstat="ACCEPTED", - role="CHAIR", - ) - line = str(a3) - self.assertIn( - "ATTENDEE;CN=Email;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:email@email.com", - line, - ) - - def test_add_attendees(self): - e = Event() - a = Attendee(email='email@email.com') - e.add_attendee(a) - lines = str(e).splitlines() - self.assertIn("ATTENDEE;CN=email@email.com:mailto:email@email.com", lines) - - def test_organizer(self): - e = Event() - e.organizer = Organizer(email='email@email.com', common_name='Mister Email') - lines = str(e).splitlines() - self.assertIn("ORGANIZER;CN=Mister Email:mailto:email@email.com", lines) - - def test_always_uid(self): - e = Event() - e.uid = None - self.assertIn('UID:', str(e)) - - def test_cmp_other(self): - with self.assertRaises(TypeError): - Event() < 1 - with self.assertRaises(TypeError): - Event() > 1 - with self.assertRaises(TypeError): - Event() <= 1 - with self.assertRaises(TypeError): - Event() >= 1 - - def test_cmp_by_name(self): - self.assertGreater(EVENT_Z, EVENT_A) - self.assertGreaterEqual(EVENT_Z, EVENT_A) - self.assertGreaterEqual(EVENT_M, EVENT_M) - - self.assertLess(EVENT_A, EVENT_Z) - self.assertLessEqual(EVENT_A, EVENT_Z) - self.assertLessEqual(EVENT_M, EVENT_M) - - def test_cmp_by_name_fail(self): - self.assertFalse(EVENT_A > EVENT_Z) - self.assertFalse(EVENT_A >= EVENT_Z) - - self.assertFalse(EVENT_Z < EVENT_A) - self.assertFalse(EVENT_Z <= EVENT_A) - - def test_cmp_by_name_fail_not_equal(self): - self.assertFalse(EVENT_A > EVENT_A) - self.assertFalse(EVENT_B < EVENT_B) - - def test_cmp_by_start_time(self): - ev1 = Event(begin=dt(2018, 6, 29, 6)) - ev2 = Event(begin=dt(2018, 6, 29, 7)) - self.assertLess(ev1, ev2) - self.assertGreaterEqual(ev2, ev1) - self.assertLessEqual(ev1, ev2) - self.assertGreater(ev2, ev1) - - def test_cmp_by_start_time_with_end_time(self): - ev1 = Event(begin=dt(2018, 6, 29, 5), end=dt(2018, 6, 29, 7)) - ev2 = Event(begin=dt(2018, 6, 29, 6), end=dt(2018, 6, 29, 8)) - ev3 = Event(begin=dt(2018, 6, 29, 6)) - self.assertLess(ev1, ev2) - self.assertGreaterEqual(ev2, ev1) - self.assertLessEqual(ev1, ev2) - self.assertGreater(ev2, ev1) - self.assertLess(ev3, ev2) - self.assertGreaterEqual(ev2, ev3) - self.assertLessEqual(ev3, ev2) - self.assertGreater(ev2, ev3) - - def test_cmp_by_end_time(self): - ev1 = Event(begin=dt(2018, 6, 29, 6), end=dt(2018, 6, 29, 7)) - ev2 = Event(begin=dt(2018, 6, 29, 6), end=dt(2018, 6, 29, 8)) - self.assertLess(ev1, ev2) - self.assertGreaterEqual(ev2, ev1) - self.assertLessEqual(ev1, ev2) - self.assertGreater(ev2, ev1) - - def test_unescape_summary(self): - c = Calendar(cal15) - e = next(iter(c.events)) - self.assertEqual(e.name, "Hello, \n World; This is a backslash : \\ and another new \n line") - - def test_unescapte_texts(self): - c = Calendar(cal17) - e = next(iter(c.events)) - self.assertEqual(e.name, "Some special ; chars") - self.assertEqual(e.location, "In, every text field") - self.assertEqual(e.description, "Yes, all of them;") - - def test_escape_output(self): - e = Event() - - e.name = "Hello, with \\ special; chars and \n newlines" - e.location = "Here; too" - e.description = "Every\nwhere ! Yes, yes !" - e.dtstamp = dt(2013, 1, 1) - e.uid = "empty-uid" - - eq = list(sorted([ - "BEGIN:VEVENT", - "DTSTAMP:20130101T000000", - "DESCRIPTION:Every\\nwhere ! Yes\\, yes !", - "LOCATION:Here\\; too", - "SUMMARY:Hello\\, with \\\\ special\\; chars and \\n newlines", - "UID:empty-uid", - "END:VEVENT" - ])) - self.assertEqual(list(sorted(str(e).splitlines())), eq) - - def test_url_input(self): - c = Calendar(cal16) - e = next(iter(c.events)) - self.assertEqual(e.url, "http://example.com/pub/calendars/jsmith/mytime.ics") - - def test_url_output(self): - URL = "http://example.com/pub/calendars/jsmith/mytime.ics" - e = Event(name="Name", url=URL) - self.assertIn("URL:" + URL, str(e).splitlines()) - - def test_status_input(self): - c = Calendar(cal16) - e = next(iter(c.events)) - self.assertEqual(e.status, "CONFIRMED") - - def test_status_output(self): - STATUS = "CONFIRMED" - e = Event(name="Name", status=STATUS) - self.assertIn("STATUS:" + STATUS, str(e).splitlines()) - - def test_category_input(self): - c = Calendar(cal16) - e = next(iter(c.events)) - self.assertIn("Simple Category", e.categories) - self.assertIn("My \"Quoted\" Category", e.categories) - self.assertIn("Category, with comma", e.categories) - - def test_category_output(self): - cat = "Simple category" - e = Event(name="Name", categories={cat}) - self.assertIn("CATEGORIES:" + cat, str(e).splitlines()) - - def test_all_day_with_end(self): - c = Calendar(cal20) - e = next(iter(c.events)) - self.assertTrue(e.all_day) - - def test_not_all_day(self): - c = Calendar(cal16) - e = next(iter(c.events)) - self.assertFalse(e.all_day) - - def test_all_day_duration(self): - c = Calendar(cal20) - e = next(iter(c.events)) - self.assertTrue(e.all_day) - self.assertEqual(e.duration, td(days=2)) - - def test_make_all_day_idempotence(self): - c = Calendar(cal18) - e = next(iter(c.events)) - self.assertFalse(e.all_day) - e2 = e.clone() - e2.make_all_day() - self.assertTrue(e2.all_day) - self.assertNotEqual(e.begin, e2.begin) - self.assertNotEqual(e.end, e2.end) - e3 = e2.clone() - e3.make_all_day() - self.assertEqual(e2.begin, e3.begin) - self.assertEqual(e2.end, e3.end) - - def test_all_day_outputs_dtstart_value_date(self): - """All day events should output DTSTART using VALUE=DATE without - time and timezone in order to assume the user's current timezone - - refs http://www.kanzaki.com/docs/ical/dtstart.html - http://www.kanzaki.com/docs/ical/date.html - """ - e = Event(begin=dt(2015, 12, 21)) - e.make_all_day() - # no time or tz specifier - self.assertIn('DTSTART;VALUE=DATE:20151221', str(e).splitlines()) - - def test_transparent_input(self): - c = Calendar(cal19) - e1 = list(c.events)[0] - self.assertEqual(e1.transparent, False) - - c2 = Calendar(cal19bis) - e2 = list(c2.events)[0] - self.assertEqual(e2.transparent, True) - - def test_default_transparent_input(self): - c = Calendar(cal18) - e = next(iter(c.events)) - self.assertEqual(e.transparent, None) - - def test_default_transparent_output(self): - e = Event(name="Name") - self.assertNotIn("TRANSP:OPAQUE", str(e).splitlines()) - - def test_transparent_output(self): - e = Event(name="Name", transparent=True) - self.assertIn("TRANSP:TRANSPARENT", str(e).splitlines()) - - e = Event(name="Name", transparent=False) - self.assertIn("TRANSP:OPAQUE", str(e).splitlines()) - - def test_includes_disjoined(self): - # disjoined events - event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=20)) - event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 50), duration=td(minutes=20)) - assert not event_a.includes(event_b) - assert not event_b.includes(event_a) - - def test_includes_intersected(self): - # intersected events - event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=30)) - event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 30), duration=td(minutes=30)) - assert not event_a.includes(event_b) - assert not event_b.includes(event_a) - - event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 30), duration=td(minutes=30)) - event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=30)) - assert not event_a.includes(event_b) - assert not event_b.includes(event_a) - - def test_includes_included(self): - # included events - event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 00), duration=td(minutes=60)) - event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=30)) - assert event_a.includes(event_b) - assert not event_b.includes(event_a) - - event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=30)) - event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 00), duration=td(minutes=60)) - assert not event_a.includes(event_b) - assert event_b.includes(event_a) - - def test_intersects_disjoined(self): - # disjoined events - event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=20)) - event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 50), duration=td(minutes=20)) - assert not event_a.intersects(event_b) - assert not event_b.intersects(event_a) - - def test_intersects_intersected(self): - # intersected events - event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=30)) - event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 30), duration=td(minutes=30)) - assert event_a.intersects(event_b) - assert event_b.intersects(event_a) - - event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 30), duration=td(minutes=30)) - event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=30)) - assert event_a.intersects(event_b) - assert event_b.intersects(event_a) - - def test_intersects_included(self): - # included events - event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 00), duration=td(minutes=60)) - event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=30)) - assert event_a.intersects(event_b) - assert event_b.intersects(event_a) - - event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=30)) - event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 00), duration=td(minutes=60)) - assert event_a.intersects(event_b) - assert event_b.intersects(event_a) - - # def test_join_disjoined(self): - # # disjoined events - # event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=20)) - # event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 50), duration=td(minutes=20)) - # with pytest.raises(ValueError): - # event_a.join(event_b) - # with pytest.raises(ValueError): - # event_b.join(event_a) - # - # def test_join_intersected(self): - # # intersected events - # event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=30)) - # event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 30), duration=td(minutes=30)) - # assert event_a.join(event_b).time_equals(Event(name=None, begin=event_a.begin, end=event_b.end)) - # assert event_b.join(event_a).time_equals(Event(name=None, begin=event_a.begin, end=event_b.end)) - # - # event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 30), duration=td(minutes=30)) - # event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=30)) - # assert event_a.join(event_b).time_equals(Event(name=None, begin=event_b.begin, end=event_a.end)) - # assert event_b.join(event_a).time_equals(Event(name=None, begin=event_b.begin, end=event_a.end)) - # - # def test_join_included(self): - # # included events - # event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 00), duration=td(minutes=60)) - # event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=30)) - # assert event_a.join(event_b).time_equals(Event(name=None, begin=event_a.begin, end=event_a.end)) - # assert event_b.join(event_a).time_equals(Event(name=None, begin=event_a.begin, end=event_a.end)) - # - # event_a = Event(name='Test #1', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=30)) - # event_b = Event(name='Test #2', begin=dt(2016, 6, 10, 20, 00), duration=td(minutes=60)) - # assert event_a.join(event_b).time_equals(Event(name=None, begin=event_b.begin, end=event_b.end)) - # assert event_b.join(event_a).time_equals(Event(name=None, begin=event_b.begin, end=event_b.end)) - # - # event = Event(uid='0', name='Test #1', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=30)) - # event.join(event) - # assert event == Event(uid='0', name='Test #1', begin=dt(2016, 6, 10, 20, 10), duration=td(minutes=30)) - - def test_issue_92(self): - c = Calendar(cal32) - e = list(c.events)[0] - - assert e.begin == dt(2016, 10, 4) - assert e.end == dt(2016, 10, 5) - - def test_classification_input(self): - c = Calendar(cal12) - e = next(iter(c.events)) - self.assertEqual(None, e.classification) - - c = Calendar(cal33_1) - e = next(iter(c.events)) - self.assertEqual('PUBLIC', e.classification) - - c = Calendar(cal33_2) - e = next(iter(c.events)) - self.assertEqual('PRIVATE', e.classification) - - c = Calendar(cal33_3) - e = next(iter(c.events)) - self.assertEqual('CONFIDENTIAL', e.classification) - - c = Calendar(cal33_4) - e = next(iter(c.events)) - self.assertEqual('iana-token', e.classification) - - c = Calendar(cal33_5) - e = next(iter(c.events)) - self.assertEqual('x-name', e.classification) - - def test_classification_output(self): - e = Event(name="Name") - self.assertNotIn("CLASS:PUBLIC", str(e).splitlines()) - - e = Event(name="Name", classification='PUBLIC') - self.assertIn("CLASS:PUBLIC", str(e).splitlines()) - - e = Event(name="Name", classification='PRIVATE') - self.assertIn("CLASS:PRIVATE", str(e).splitlines()) - - e = Event(name="Name", classification='CONFIDENTIAL') - self.assertIn("CLASS:CONFIDENTIAL", str(e).splitlines()) - - e = Event(name="Name", classification='iana-token') - self.assertIn("CLASS:iana-token", str(e).splitlines()) - - e = Event(name="Name", classification='x-name') - self.assertIn("CLASS:x-name", str(e).splitlines()) - - def test_classification_bool(self): - with pytest.raises(TypeError): - Event(name="Name", classification=True) - - def test_last_modified(self): - c = Calendar(cal18) - e = list(c.events)[0] - self.assertEqual(dt(2015, 11, 13, 00, 48, 9, tzinfo=tzutc), e.last_modified) - - def equality(self): - ev1 = Event(begin=dt(2018, 6, 29, 5), end=dt(2018, 6, 29, 7), name="my name") - ev2 = ev1.clone() - - assert ev1 == ev2 - - ev2.uid = "something else" - assert ev1 == ev2 - - ev2.name = "other name" - assert ev1 != ev2 - - def test_attendee_parse(self): - with open( - os.path.join(os.path.dirname(__file__), "fixtures/groupscheduled.ics") - ) as f: - c = Calendar(f.read()) - e = list(c.events)[0] - assert len(e.attendees) == 1 diff --git a/tests/fixture.py b/tests/fixture.py deleted file mode 100644 index ee30f5e2..00000000 --- a/tests/fixture.py +++ /dev/null @@ -1,766 +0,0 @@ -from __future__ import unicode_literals - -cal1 = """ -BEGIN:VCALENDAR -METHOD:PUBLISH -VERSION:2.0 -X-WR-CALNAME:plop -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -X-APPLE-CALENDAR-COLOR:#882F00 -X-WR-TIMEZONE:Europe/Brussels -CALSCALE:GREGORIAN -BEGIN:VTIMEZONE -TZID:Europe/Brussels -BEGIN:DAYLIGHT -TZOFFSETFROM:+0100 -RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU -DTSTART:19810329T020000 -TZNAME:UTC+2 -TZOFFSETTO:+0200 -END:DAYLIGHT -BEGIN:STANDARD -TZOFFSETFROM:+0200 -RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU -DTSTART:19961027T030000 -TZNAME:UTC+1 -TZOFFSETTO:+0100 -END:STANDARD -END:VTIMEZONE -BEGIN:VEVENT -CREATED:20131024T204716Z -UID:ABBF2903-092F-4202-98B6-F757437A5B28 -DTEND;TZID=Europe/Brussels:20131029T113000 -TRANSP:OPAQUE -SUMMARY:dfqsdfjqkshflqsjdfhqs fqsfhlqs dfkqsldfkqsdfqsfqsfqsfs -DTSTART;TZID=Europe/Brussels:20131029T103000 -DTSTAMP:20131024T204741Z -SEQUENCE:3 -DESCRIPTION:Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed - vitae facilisis enim. Morbi blandit et lectus venenatis tristique. Donec - sit amet egestas lacus. Donec ullamcorper, mi vitae congue dictum, quam - dolor luctus augue, id cursus purus justo vel lorem. Ut feugiat enim ips - um, quis porta nibh ultricies congue. Pellentesque nisl mi, molestie id - sem vel, vehicula nullam. -END:VEVENT -BEGIN:VTODO -DTSTAMP:20180218T154700Z -UID:Uid -DESCRIPTION:Lorem ipsum dolor sit amet. -PERCENT-COMPLETE:0 -PRIORITY:0 -SUMMARY:Name -END:VTODO -END:VCALENDAR -""" - -cal2 = """ -BEGIN:VCALENDAR -BEGIN:VEVENT -DTEND;TZID=Europe/Berlin:20120608T212500 -SUMMARY:Name -DTSTART;TZID=Europe/Berlin:20120608T202500 -LOCATION:MUC -SEQUENCE:0 -BEGIN:VALARM -TRIGGER:PT1H -DESCRIPTION:Event reminder -ACTION:DISPLAY -END:VALARM -END:VEVENT -END:VCALENDAR -""" - - -cal3 = """ -BEGIN:VCALENDAR -END:VCALENDAR -""" - -cal4 = """BEGIN:VCALENDAR""" - -cal5 = """ -BEGIN:VCALENDAR -VERSION:2.0 -END:VCALENDAR -""" - -cal6 = """ -DESCRIPTION:a - b -""" - -cal7 = """ -BEGIN:VCALENDAR - -END:VCALENDAR -""" - -cal8 = """ -BEGIN:VCALENDAR -\t -END:VCALENDAR -""" - -cal9 = """ - -BEGIN:VCALENDAR -END:VCALENDAR -""" - -cal10 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -BEGIN:VEVENT -DTEND;TZID=Europe/Berlin:20120608T212500 -DTSTAMP:20131024T204741Z -SUMMARY:Name -DTSTART;TZID=Europe/Berlin:20120608T202500 -LOCATION:MUC -SEQUENCE:0 -BEGIN:VALARM -TRIGGER:PT1H -DESCRIPTION:Event reminder -ACTION:DISPLAY -END:VALARM -END:VEVENT -END:VCALENDAR -""" - -cal11 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -END:VCAL -""" - -cal12 = """ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.8//EN -BEGIN:VEVENT -SUMMARY:Name -DTSTART;TZID=Europe/Berlin:20120608T202500 -DURATION:P1DT1H -LOCATION:MUC -GEO:40.779897;-73.968565 -SEQUENCE:0 -BEGIN:VALARM -TRIGGER:PT1H -DESCRIPTION:Event reminder -ACTION:DISPLAY -END:VALARM -END:VEVENT -END:VCALENDAR -""" - -cal13 = """ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -BEGIN:VEVENT -SUMMARY:Name -DTSTART;TZID=Europe/Berlin:20120608T202500 -DTEND;TZID=Europe/Berlin:20120608T212500 -DURATION:P1DT1H -LOCATION:MUC -SEQUENCE:0 -BEGIN:VALARM -TRIGGER:PT1H -DESCRIPTION:Event reminder -ACTION:DISPLAY -END:VALARM -END:VEVENT -END:VCALENDAR -""" - -cal14 = u""" -BEGIN:VCALENDAR -VERSION:2.0;42 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -END:VCALENDAR -""" - -cal15 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN - -BEGIN:VEVENT -SUMMARY:Hello, \\n World\\; This is a backslash : \\\\ and another new \\N line -DTSTART;TZID=Europe/Berlin:20120608T202500 -DTEND;TZID=Europe/Berlin:20120608T212500 -LOCATION:MUC -END:VEVENT - -END:VCALENDAR -""" - -# Event with URL and STATUS -cal16 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN - -BEGIN:VEVENT -SUMMARY:Hello, \\n World\\; This is a backslash : \\\\ and another new \\N line -DTSTART;TZID=Europe/Berlin:20120608T202500 -DTEND;TZID=Europe/Berlin:20120608T212500 -LOCATION:MUC -URL:http://example.com/pub/calendars/jsmith/mytime.ics -STATUS:CONFIRMED -CATEGORIES:Simple Category,My "Quoted" Category,Category\\, with comma -END:VEVENT - -END:VCALENDAR -""" - -cal17 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN - -BEGIN:VEVENT -SUMMARY:Some special \\; chars -DTSTART;TZID=Europe/Berlin:20130608T202501 -DTEND;TZID=Europe/Berlin:20130608T212501 -LOCATION:In\\, every text field -DESCRIPTION:Yes\\, all of them\\; -END:VEVENT -END:VCALENDAR -""" - - -# long event which is not all_day -cal18 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:ownCloud Calendar 0.7.3 -X-WR-CALNAME:test -BEGIN:VEVENT -UID:3912dcd3d4 -DTSTAMP:20151113T004809Z -CREATED:20151113T004809Z -LAST-MODIFIED:20151113T004809Z -SUMMARY:long event -DTSTART;TZID=Europe/Berlin:20151113T140000 -DTEND;TZID=Europe/Berlin:20151124T080000 -LOCATION: -DESCRIPTION: -CATEGORIES: -END:VEVENT -END:VCALENDAR -""" - -# Event with TRANSP -cal19 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN - -BEGIN:VEVENT -SUMMARY:E1 -TRANSP:OPAQUE -END:VEVENT - -END:VCALENDAR -""" - - -# Event with TRANSP -cal19bis = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN - -BEGIN:VEVENT -SUMMARY:E2 -TRANSP:TRANSPARENT -END:VEVENT -END:VCALENDAR -""" - -# 2 days all-day event -cal20 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:manually crafted from an ownCloud 8.0 ics -BEGIN:VEVENT -SUMMARY:2 days party -DTSTART;VALUE=DATE:20151114 -DTEND;VALUE=DATE:20151116 -END:VEVENT -END:VCALENDAR -""" - -# Event with Display alarm without repeats -cal21 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN - -BEGIN:VEVENT -SUMMARY:Some special \\; chars -DTSTART;TZID=Europe/Berlin:20130608T202501 -DTEND;TZID=Europe/Berlin:20130608T212501 -LOCATION:In\\, every text field -DESCRIPTION:Yes\\, all of them\\; -BEGIN:VALARM -TRIGGER:PT1H -DESCRIPTION:Event reminder -ACTION:DISPLAY -END:VALARM -END:VEVENT -END:VCALENDAR -""" - -# Event with Display alarm WITH repeats -cal22 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN - -BEGIN:VEVENT -SUMMARY:Some special \\; chars -DTSTART;TZID=Europe/Berlin:20130608T202501 -DTEND;TZID=Europe/Berlin:20130608T212501 -LOCATION:In\\, every text field -DESCRIPTION:Yes\\, all of them\\; -BEGIN:VALARM -TRIGGER:PT1H -REPEAT:2 -DURATION:PT10M -DESCRIPTION:Event reminder -ACTION:DISPLAY -END:VALARM -END:VEVENT -END:VCALENDAR -""" - -# Event with Display alarm without repeats -cal23 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN - -BEGIN:VEVENT -SUMMARY:Some special \\; chars -DTSTART;TZID=Europe/Berlin:20130608T202501 -DTEND;TZID=Europe/Berlin:20130608T212501 -LOCATION:In\\, every text field -DESCRIPTION:Yes\\, all of them\\; -BEGIN:VALARM -TRIGGER;VALUE=DATE-TIME:20160101T000000Z -DESCRIPTION:Event reminder -ACTION:DISPLAY -END:VALARM -END:VEVENT -END:VCALENDAR -""" - -# Event with AUDIO alarm without attach -cal24 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN - -BEGIN:VEVENT -SUMMARY:Some special \\; chars -DTSTART;TZID=Europe/Berlin:20130608T202501 -DTEND;TZID=Europe/Berlin:20130608T212501 -LOCATION:In\\, every text field -DESCRIPTION:Yes\\, all of them\\; -BEGIN:VALARM -TRIGGER;VALUE=DATE-TIME:20160101T000000Z -ACTION:AUDIO -END:VALARM -END:VEVENT -END:VCALENDAR -""" - -# Event with AUDIO alarm WITH attach -cal25 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN - -BEGIN:VEVENT -SUMMARY:Some special \\; chars -DTSTART;TZID=Europe/Berlin:20130608T202501 -DTEND;TZID=Europe/Berlin:20130608T212501 -LOCATION:In\\, every text field -DESCRIPTION:Yes\\, all of them\\; -BEGIN:VALARM -TRIGGER;VALUE=DATE-TIME:20160101T000000Z -ACTION:AUDIO -ATTACH;FMTTYPE=audio/basic:ftp://example.com/pub/sounds/bell-01.aud -END:VALARM -END:VEVENT -END:VCALENDAR -""" - -# Event with a tabbed line fold -cal26 = u""" -BEGIN:VCALENDAR -BEGIN:VEVENT -DTEND;TZID=Europe/Berlin:20120608T212500 -SUMMARY:Name -DTSTART;TZID=Europe/Berlin:20120608T202500 -LOCATION:MUC -SEQUENCE:0 -UID:040000008200E00074C5B7101A82E0080000000050B9861DFE30D101000000000000000 - 010000000DC18788D5CDAF947A99D8A91D04C601C -BEGIN:VALARM -TRIGGER:PT1H -DESCRIPTION:Event reminder -ACTION:DISPLAY -END:VALARM -END:VEVENT -END:VCALENDAR -""" - -# All VTODO attributes beside duration. -cal27 = """ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -BEGIN:VTODO -DTSTAMP:20180218T154700Z -UID:Uid -COMPLETED:20180418T150000Z -CREATED:20180218T154800Z -DESCRIPTION:Lorem ipsum dolor sit amet. -DTSTART:20180218T164800Z -LOCATION:Earth -PERCENT-COMPLETE:0 -PRIORITY:0 -SUMMARY:Name -URL:https://www.example.com/cal.php/todo.ics -DURATION:PT10M -SEQUENCE:0 -BEGIN:VALARM -TRIGGER:PT1H -DESCRIPTION:Event reminder -ACTION:DISPLAY -END:VALARM -END:VTODO -END:VCALENDAR -""" - -# Test due. -cal28 = """ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -BEGIN:VTODO -DTSTAMP:20180218T154741Z -UID:Uid -DUE:20180218T164800Z -END:VTODO -END:VCALENDAR -""" - -# Test error due and duration. -cal29 = """ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -BEGIN:VTODO -DTSTAMP:20180218T154741Z -UID:Uid -DURATION:PT10M -DUE:20180218T164800Z -END:VTODO -END:VCALENDAR -""" - -cal30 = """ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -BEGIN:VTODO -DTSTAMP:20180218T154741Z -UID:Uid -DUE:20180218T164800Z -DURATION:PT10M -END:VTODO -END:VCALENDAR -""" - -cal31 = """ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -BEGIN:VTODO -DTSTAMP:20180218T154741Z -UID:Uid -SUMMARY:Hello, \\n World\\; This is a backslash : \\\\ and another new \\N line -LOCATION:In\\, every text field -DESCRIPTION:Yes\\, all of them\\; -END:VTODO -END:VCALENDAR -""" - -cal32 = """ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:- - -BEGIN:VEVENT -DTSTART;VALUE=DATE:20161004 -DTEND;VALUE=DATE:20161005 -SUMMARY:An all day event: October 4 2016. -END:VEVENT - -END:VCALENDAR -""" - -clas33 = """ -BEGIN:VTIMEZONE -TZID:Australia/Sydney -TZURL:http://tzurl.org/zoneinfo/Australia/Sydney -SEQUENCE:498 -SEQUENCE:498 -BEGIN:STANDARD -TZOFFSETFROM:+1100 -TZOFFSETTO:+1000 -TZNAME:EST -DTSTART:20080406T030000 -RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU -END:STANDARD -""" - -cal34 = """ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN - -BEGIN:VEVENT -SUMMARY:a -END:VEVENT - -END:VCALENDAR - -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN - -BEGIN:VEVENT -SUMMARY:b -END:VEVENT - -END:VCALENDAR -""" - -unfolded_cal2 = [ - 'BEGIN:VCALENDAR', - 'BEGIN:VEVENT', - 'DTEND;TZID=Europe/Berlin:20120608T212500', - 'SUMMARY:Name', - 'DTSTART;TZID=Europe/Berlin:20120608T202500', - 'LOCATION:MUC', - 'SEQUENCE:0', - 'BEGIN:VALARM', - 'TRIGGER:PT1H', - 'DESCRIPTION:Event reminder', - 'ACTION:DISPLAY', - 'END:VALARM', - 'END:VEVENT', - 'END:VCALENDAR', -] - -unfolded_cal1 = [ - 'BEGIN:VCALENDAR', - 'METHOD:PUBLISH', - 'VERSION:2.0', - 'X-WR-CALNAME:plop', - 'PRODID:-//Apple Inc.//Mac OS X 10.9//EN', - 'X-APPLE-CALENDAR-COLOR:#882F00', - 'X-WR-TIMEZONE:Europe/Brussels', - 'CALSCALE:GREGORIAN', - 'BEGIN:VTIMEZONE', - 'TZID:Europe/Brussels', - 'BEGIN:DAYLIGHT', - 'TZOFFSETFROM:+0100', - 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU', - 'DTSTART:19810329T020000', - 'TZNAME:UTC+2', - 'TZOFFSETTO:+0200', - 'END:DAYLIGHT', - 'BEGIN:STANDARD', - 'TZOFFSETFROM:+0200', - 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU', - 'DTSTART:19961027T030000', - 'TZNAME:UTC+1', - 'TZOFFSETTO:+0100', - 'END:STANDARD', - 'END:VTIMEZONE', - 'BEGIN:VEVENT', - 'CREATED:20131024T204716Z', - 'UID:ABBF2903-092F-4202-98B6-F757437A5B28', - 'DTEND;TZID=Europe/Brussels:20131029T113000', - 'TRANSP:OPAQUE', - 'SUMMARY:dfqsdfjqkshflqsjdfhqs fqsfhlqs dfkqsldfkqsdfqsfqsfqsfs', - 'DTSTART;TZID=Europe/Brussels:20131029T103000', - 'DTSTAMP:20131024T204741Z', - 'SEQUENCE:3', - 'DESCRIPTION:Lorem ipsum dolor sit amet, consectetur adipiscing elit. \ -Sedvitae facilisis enim. Morbi blandit et lectus venenatis tristique. \ -Donecsit amet egestas lacus. Donec ullamcorper, mi vitae congue dictum, \ -quamdolor luctus augue, id cursus purus justo vel lorem. \ -Ut feugiat enim ipsum, quis porta nibh ultricies congue. \ -Pellentesque nisl mi, molestie idsem vel, vehicula nullam.', - 'END:VEVENT', - 'BEGIN:VTODO', - 'DTSTAMP:20180218T154700Z', - 'UID:Uid', - 'DESCRIPTION:Lorem ipsum dolor sit amet.', - 'PERCENT-COMPLETE:0', - 'PRIORITY:0', - 'SUMMARY:Name', - 'END:VTODO', - 'END:VCALENDAR', -] - -unfolded_cal6 = ['DESCRIPTION:ab'] - -unfolded_cal21 = [ - 'BEGIN:VCALENDAR', - 'BEGIN:VEVENT', - 'DTEND;TZID=Europe/Berlin:20120608T212500', - 'SUMMARY:Name', - 'DTSTART;TZID=Europe/Berlin:20120608T202500', - 'LOCATION:MUC', - 'SEQUENCE:0', - 'BEGIN:VALARM', - 'TRIGGER:PT1H', - 'REPEAT:2', - 'DURATION:PT10M', - 'DESCRIPTION:Event reminder', - 'ACTION:DISPLAY', - 'END:VALARM', - 'END:VEVENT', - 'END:VCALENDAR', -] - -unfolded_cal26 = [ - 'BEGIN:VCALENDAR', - 'BEGIN:VEVENT', - 'DTEND;TZID=Europe/Berlin:20120608T212500', - 'SUMMARY:Name', - 'DTSTART;TZID=Europe/Berlin:20120608T202500', - 'LOCATION:MUC', - 'SEQUENCE:0', - 'UID:040000008200E00074C5B7101A82E0080000000050B9861DFE30D101000000000000000010000000DC18788D5CDAF947A99D8A91D04C601C', - 'BEGIN:VALARM', - 'TRIGGER:PT1H', - 'DESCRIPTION:Event reminder', - 'ACTION:DISPLAY', - 'END:VALARM', - 'END:VEVENT', - 'END:VCALENDAR', -] - -cal33_1 = """ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -BEGIN:VEVENT -DTEND;TZID=Europe/Berlin:20120608T212500 -SUMMARY:Name -DTSTART;TZID=Europe/Berlin:20120608T202500 -LOCATION:MUC -SEQUENCE:0 -CLASS:PUBLIC -END:VEVENT -END:VCALENDAR -""" - -cal33_2 = """ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -BEGIN:VEVENT -DTEND;TZID=Europe/Berlin:20120608T212500 -SUMMARY:Name -DTSTART;TZID=Europe/Berlin:20120608T202500 -LOCATION:MUC -SEQUENCE:0 -CLASS:PRIVATE -END:VEVENT -END:VCALENDAR -""" - -cal33_3 = """ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -BEGIN:VEVENT -DTEND;TZID=Europe/Berlin:20120608T212500 -SUMMARY:Name -DTSTART;TZID=Europe/Berlin:20120608T202500 -LOCATION:MUC -SEQUENCE:0 -CLASS:CONFIDENTIAL -END:VEVENT -END:VCALENDAR -""" - -cal33_4 = """ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -BEGIN:VEVENT -DTEND;TZID=Europe/Berlin:20120608T212500 -SUMMARY:Name -DTSTART;TZID=Europe/Berlin:20120608T202500 -LOCATION:MUC -SEQUENCE:0 -CLASS:iana-token -END:VEVENT -END:VCALENDAR -""" - -cal33_5 = """ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN -BEGIN:VEVENT -DTEND;TZID=Europe/Berlin:20120608T212500 -SUMMARY:Name -DTSTART;TZID=Europe/Berlin:20120608T202500 -LOCATION:MUC -SEQUENCE:0 -CLASS:x-name -END:VEVENT -END:VCALENDAR -""" - - -cal35 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN - -BEGIN:VEVENT -BEGIN:VALARM -TRIGGER;VALUE=DATE-TIME:20160101T000000Z -ACTION:NONE -END:VALARM -END:VEVENT -END:VCALENDAR -""" - -cal36 = u""" -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Apple Inc.//Mac OS X 10.9//EN - -BEGIN:VEVENT -BEGIN:VALARM -TRIGGER;VALUE=DATE-TIME:20160101T000000Z -ACTION:YOLO -END:VALARM -END:VEVENT -END:VCALENDAR -""" diff --git a/tests/fixtures/README b/tests/fixtures/README deleted file mode 100644 index 5256c86b..00000000 --- a/tests/fixtures/README +++ /dev/null @@ -1,35 +0,0 @@ -encoding.ics, multiple.ics, small.ics, timezoned.ics, case_meetup.ics, groupscheduled.ics, recurrence.ics and time.ics are Copyright (c) 2012-2013, Plone Foundation under the BSD License and modified to suit ics.py needs. Source : https://pypi.python.org/pypi/icalendar - -utf-8-emoji.ics contains data with the following Copyright: - Markus Kuhn [ˈmaʳkʊs kuːn] — 2002-07-25 CC BY -and - © 2019 Unicode®, Inc. - Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. - For terms of use, see http://www.unicode.org/terms_of_use.html - -Romeo-and-Juliet.txt and Romeo-and-Juliet.ics contains data exported from Wikisource on 02/23/20: - This e-book comes from the online library Wikisource[1]. This multilingual digital library, built by volunteers, is committed to developing a free accessible collection of publications of every kind: novels, poems, magazines, letters... - We distribute our books for free, starting from works not copyrighted or published under a free license. You are free to use our e-books for any purpose (including commercial exploitation), under the terms of the Creative Commons Attribution-ShareAlike 3.0 Unported[2] license or, at your choice, those of the GNU FDL[3]. - Wikisource is constantly looking for new members. During the realization of this book, it's possible that we made some errors. You can report them at this page[4]. - - The following users contributed to this book: - Angelprincess72 - Djr13 - ThomasBot - BirgitteSB - Mpaa - Beleg Tâl - Einstein95 - Kathleen.wright5 - EncycloPetey - Dariyman - - * * * - - ↑ http://wikisource.org - - ↑ http://www.creativecommons.org/licenses/by-sa/3.0 - - ↑ http://www.gnu.org/copyleft/fdl.html - - ↑ http://wikisource.org/wiki/Wikisource:Scriptorium diff --git a/tests/fixtures/Romeo-and-Juliet.ics b/tests/fixtures/Romeo-and-Juliet.ics deleted file mode 100644 index 63da8c57..00000000 --- a/tests/fixtures/Romeo-and-Juliet.ics +++ /dev/null @@ -1,556 +0,0 @@ -BEGIN:VCALENDAR -PRODID:-//Nextcloud calendar v1.7.2 -VERSION:2.0 -CALSCALE:GREGORIAN -BEGIN:VEVENT -CREATED:20200223T183124 -DTSTAMP:20200223T183124 -LAST-MODIFIED:20200223T183124 -UID:JCGUPMSIMHOT80Q0CN3NV -SUMMARY:Theatre -CLASS:PUBLIC -DESCRIPTION:Romeo and Juliet (The Illustrated Shakespeare)\n\n\n Willia - m Shakespeare\n\n\n\n\n\n1847\n\n\n\n\n\nExported from Wikisource on 02/23/ - 20\n\n\n\n\n\nThis work may need to be standardized using Wikisource's styl - e guidelines.\n\nIf you'd like to help\, please review the help pages.\n\n\ - n\n\n\nPROLOGUE\n\n\n​ PROLOGUE\n\n\n\nCHORUS.\n\n\n\nTwo households\, both - alike in dignity\,\n\nIn fair Verona\, where we lay our scene\,\n\nFrom an - cient grudge break to new mutiny\,\n\nWhere civil blood makes civil hands u - nclean.\n\nFrom forth the fatal loins of these two foes\n\nA pair of star-c - ross'd lovers take their life\;\n\nWhose misadventur'd piteous overthrows\n - \nDo\, with their death\, bury their parents' strife.\n\nThe fearful passag - e of their death-mark'd love\,\n\nAnd the continuance of their parents' rag - e\,\n\nWhich\, but their children's end\, nought could remove\,\n\nIs now t - he two hours' traffic of our stage\;\n\nThe which if you with patient ears - attend\,\n\nWhat here shall miss\, our toil shall strive to mend.\n\n\n\n\n - \nAn image should appear at this position in the text.\n\nIf you are able t - o provide it\, see Wikisource:Image guidelines and Help:Adding images for g - uidance.\n\n\n\n\n\nACT I\n\n\n​\n\n\n\nScene I.—A Public Place.\n\nEnter S - ampson and Gregory\, armed with Swords and Bucklers.\n\n\n\nSam. Gregory\, - on my word\, we'll not carry coals.\n\nGre. No\, for then we should be coll - iers.\n\nSam. I mean\, an we be in choler\, we'll draw.\n\nGre. Ay\, while - you live\, draw your neck out of the collar.\n\nSam. I strike quickly\, bei - ng moved.\n\nGre. But thou art not quickly moved to strike.\n\nSam. A dog o - f the house of Montague moves me.\n\nGre. To move is to stir\, and to be va - liant is to stand: therefore\, if thou art moved\, thou run'st away.\n\nSam - . A dog of that house shall move me to stand. I will take the wall of any m - an or maid of Montague's.\n\nGre. That shows thee a weak slave\; for the we - akest goes to the wall.\n\nSam. 'Tis true\; and therefore women\, being the - weaker vessels\,are ever thrust to the wall:—therefore\, I will push Monta - gue's men from the wall\, and thrust his maids to the wall.\n\nGre. The qua - rrel is between our masters\, and us their men.\n\nSam. 'Tis all one\, I wi - ll show myself a tyrant: when I have fought with the men\, I will be civil - with the maids\; I will cut off their heads.\n\nGre. The heads of the maids - ?\n\nSam. Ay\, the heads of the maids\, or their maidenheads\; take it in w - hat sense thou wilt.\n\nGre. They must take it in sense\, that feel it.\n\n - Sam. Me they shall feel\, while I am able to stand\; and 'tis known\, I am - a pretty piece of flesh.\n\nGre. 'Tis well\, thou art not fish\; if thou ha - dst\, ​thou hadst been poor John. Draw thy tool\; here comes two of the hou - se of the Montagues.\n\n\n\n\n\nEnter Abraham and Balthasar.\n\n\n\nSam. My - naked weapon is out: quarrel\, I will back thee.\n\nGre. How! turn thy bac - k\, and run?\n\nSam. Fear me not.\n\nGre. No marry: I fear thee!\n\nSam. Le - t us take the law of our sides\; let them begin.\n\nGre. I will frown as I - pass by\, and let them take it as they list.\n\nSam. Nay\, as they dare. I - will bite my thumb at them\; which is a disgrace to them\, if they bear it. - \n\nAbr. Do you bite your thumb at us\, sir?\n\nSam. I do bite my thumb\, s - ir.\n\nAbr. Do you bite your thumb at us\, sir?\n\n\n\nAn image should appe - ar at this position in the text.\n\nIf you are able to provide it\, see Wik - isource:Image guidelines and Help:Adding images for guidance.\n\n\n\nSam. I - s the law of our side\, if I say—ay?\n\nGre. No.\n\nSam. No\, sir\, I do no - t bite my thumb at you\, sir\; but I bite my thumb\, sir.\n\nGre. Do you qu - arrel\, sir?\n\nAbr. Quarrel\, sir? no\, sir.\n\nSam. If you do\, sir\, I a - m for you: I serve as good a man as you.\n\nAbr. No better.\n\nSam. Well\, - sir.\n\n\n\n\n\nEnter Benvolio\, at a distance.\n\n\n\nGre. Say—better: her - e comes one of my master's kinsmen.\n\nSam. Yes\, better\, sir.\n\nAbr. You - lie.\n\nSam. Draw\, if you be men.—Gregory\, remember thy swashing blow. [ - They fight.\n\nBen. Part\, fools! put up your swords\; you know not what yo - u do. [Beats down their Swords.\n\n\n\n\n\nEnter Tybalt.\n\n\n\nTyb. What! - art thou drawn among these heartless hinds?\n\nTurn thee\, Benvolio\, look - upon thy death.\n\nBen. I do but keep the peace: put up thy sword\,\n\nOr m - anage it to part these men with me.\n\nTyb. What! drawn\, and talk of peace - ? I hate the word\,\n\nAs I hate hell\, all Montagues\, and thee.\n\n\n\nHa - ve at thee\, coward. [They fight.\n\n​ Enter several persons of both Houses - \, who join the fray\; then enter Citizens\, with clubs or partisans.\n\n\n - \n1 Cit. Clubs\, bills\, and partisans! strike! beat them down!\n\nDown wit - h the Capulets! down with the Montagues!\n\n\n\n\n\nEnter Capulet in his go - wn\; and Lady Capulet.\n\n\n\nCap. What noise is this?—Give me my long swor - d\, ho!\n\nLa. Cap. A crutch\, a crutch!—Why call you for a sword?\n\nCap. - My sword\, I say!—Old Montague is come\,\n\nAnd flourishes his blade in spi - te of me.\n\n\n\n\n\nEnter Montague and Lady Montague.\n\n\n\nMon. Thou vil - lain Capulet!—Hold me not\; let me go.\n\nLa. Mon. Thou shalt not stir one - foot to seek a foe.\n\n\n\n\n\nEnter Prince\, with his train.\n\n\n\nPrin. - Rebellious subjects\, enemies to peace\,\n\nProfaners of this neighbour-sta - ined steel—\n\nWill they not hear?—what ho! you men\, you beasts\,\n\nThat - quench the fire of your pernicious rage\n\nWith purple fountains issuing fr - om your veins\,\n\nOn pain of torture\, from those bloody hands\n\nThrow yo - ur mis–temper'd weapons to the ground\,\n\nAnd hear the sentence of your mo - ved prince.—\n\nThree civil brawls\, bred of an airy word\,\n\nBy thee\, ol - d Capulet\, and Montague\,\n\nHave thrice disturb'd the quiet of our street - s\;\n\nAnd made Verona's ancient citizens\n\nCast by their grave beseeming - ornaments\,\n\nTo wield old partisans\, in hands as old\,\n\nCanker'd with - peace\, to part your canker'd hate.\n\nIf ever you disturb our streets agai - n\,\n\nYour lives shall pay the forfeit of the peace:\n\nFor this time\, al - l the rest depart away.\n\nYou Capulet\, shall go along with me\;\n\nAnd\, - Montague\, come you this afternoon\,\n\nTo know our further pleasure in thi - s case\,\n\nTo old Free-town\, our common judgment-place.\n\n\n\nOnce more\ - , on pain of death\, all men depart. [Exeunt Prince and Attendants\; Capule - t\, Lady Capulet\, Tybalt\, Citizens\, and Servants.\n\nMon. Who set this a - ncient quarrel new abroach?\n\nSpeak\, nephew\, were you by when it began?\ - n\nBen. Here were the servants of your adversary\,\n\nAnd yours\, close fig - hting ere I did approach.\n\nI drew to part them: in the instant came\n\nTh - e fiery Tybalt\, with his sword prepar'd\;\n\nWhich\, as he breath'd defian - ce to my ears\,\n\nHe swung about his head\, and cut the winds\,\n\nWho\, n - othing hurt withal\, hiss'd him in scorn.\n\nWhile we were interchanging th - rusts and blows\,\n\nCame more and more\, and fought on part and part\,\n\n - Till the prince came\, who parted either part.\n\nLa. Mon. O! where is Rome - o?—saw you him to-day?\n\nRight glad I am he was not at this fray.\n\nBen. - Madam\, an hour before the worshipp'd sun\n\nPeer'd forth the golden window - of the east\,\n\nA troubled mind drave me to walk abroad\;\n\nWhere\, unde - rneath the grove of sycamore\n\nThat westward rooteth from the city's side\ - ,\n\nSo early walking did I see your son.\n\nTowards him I made\; but he wa - s 'ware of me\,\n\nAnd stole into the covert of the wood:\n\nI\, measuring - his affections by my own\,\n\nWhich then most sought\, where most might not - be found\,\n\nBeing one too many by my weary self\,\n\nPursu'd my humour\, - not pursuing his\,\n\nAnd gladly shunn'd who gladly fled from me.\n\nMon. - Many a morning hath he there been seen\,\n\nWith tears augmenting the fresh - morning's dew\,\n\nAdding to clouds more clouds with his deep sighs:\n\nBu - t all so soon as the all-cheering sun\n\nShould in the furthest east begin - to draw\n\nThe shady curtains from Aurora's bed\,\n\nAway from the light st - eals home my heavy son\,\n\nAnd private in his chamber pens himself\;\n\nSh - uts up his windows\, locks fair daylight out\,\n\nAnd makes himself an arti - ficial night.\n\nBlack and portentous must this humour prove\,\n\nUnless go - od counsel may the cause remove.\n\nBen. My noble uncle\, do you know the c - ause?\n\nMon. I neither know it\, nor can learn of him.\n\nBen. Have you im - portun'd him by any means?\n\nMon. Both by myself\, and many other friends: - \n\nBut he\, his own affections' counsellor\,\n\nIs to himself—I will not s - ay\, how true—\n\nBut to himself so secret and so close\,\n\nSo far from so - unding and discovery\,\n\nAs is the bud bit with an envious worm.\n\nEre he - can spread his sweet leaves to the air\,\n\nOr dedicate his beauty to the - sun.\n\nCould we but learn from whence his sorrows grow\,\n\nWe would as wi - llingly give cure\, as know.\n\n\n\n\n\nEnter Romeo\, at a distance.\n\n\n\ - nBen. See\, where he comes: so please you\, step aside\;\n\nI'll know his g - rievance\, or be much denied.\n\nMon. I would\, thou wert so happy by thy s - tay\,\n\n\n\nTo hear true shrift.—Come\, madam\, let's away. [Exeunt Montag - ue and Lady.\n\nBen. Good morrow\, cousin.\n\nRom. Is the day so young?\n\n - Ben. But new struck nine.\n\nRom. Ah me! sad hours seem long.\n\nWas that m - y father that went hence so fast?\n\nBen. It was. What sadness lengthens Ro - meo's hours?\n\nRom. Not having that\, which\, having\, makes them short.\n - \nBen. In love?\n\nRom. Out.\n\nBen. Of love?\n\nRom. Out of her favour\, w - here I am in love.\n\nBen. Alas\, that love\, so gentle in his view\,\n\nSh - ould be so tyrannous and rough in proof!\n\nRom. Alas\, that love\, whose v - iew is muffled still\,\n\nShould\, without eyes\, see pathways to his will! - \n\nWhere shall we dine?—O me!—What fray was here?\n\nYet tell me not\, for - I have heard it all.\n\nHere's much to do with hate\, but more with love:\ - n\nWhy then\, O brawling love! O loving hate!\n\nO any thing\, of nothing f - irst created!\n\nO heavy lightness! serious vanity!\n\nMis-shapen chaos of - well-seeming forms!\n\nFeather of lead\, bright smoke\, cold fire\, sick he - alth!\n\nStill-waking sleep\, that is not what it is!—\n\nThis love feel I\ - , that feel no love in this.\n\nDost thou not laugh?\n\nBen. No\, coz\; I r - ather weep.\n\nRom. Good heart\, at what?\n\n\n\n​Ben. At thy good heart's - oppression.\n\nRom. Why\, such is love's transgression.-\n\nGriefs of mine - own lie heavy in my breast\;\n\nWhich thou wilt propagate\, to have it pres - se'd\n\nWith more of thine: this love\, that thou hast shown\,\n\nDoth add - more grief to too much of mine own.\n\nLove is a smoke\, made with the fume - of sighs\;\n\nBeing purg'd\, a fire sparkling in lovers' eyes\;\n\nBeing v - ex'd\, a sea nourish'd with lover's tears:\n\nWhat is it else? a madness mo - st discreet\,\n\nA choking gall\, and a preserving sweet.\n\n\n\nFarewell\, - my coz. [Going.\n\nBen. Soft\, I will go along:\n\nAnd if you leave me so\ - , you do me wrong.\n\nRom. Tut! I have lost myself: I am not here\;\n\nThis - is not Romeo\, he's some other where.\n\nBen. Tell me in sadness\, who is - it that you love.\n\nRom. What! shall I groan\, and tell thee?\n\nBen Groan - ! why\, no\;\n\nBut sadly tell me\, who.\n\nRom. Bid a sick man in sadness - make his will\;\n\nA word ill urg'd to one that is so ill.-\n\nIn sadness\, - cousin\, I do love a woman.\n\nBen. I aim'd so near\, when I suppos'd you - lov'd.\n\nRom. A right good mark-man! And she's fair I love.\n\nBen. A righ - t fair mark\, fair coz\, is soonest hit.\n\nRom. Well\, in that hit\, you m - iss: she'll not be hit.\n\nWith Cupid's arrow. She hath Dian's wit\;\n\nAnd - in strong proof of chastity well arm'd\,\n\nFrom love's weak childish bow - she lives unharm'd\n\nShe will not stay the siege of loving terms\,\n\nNor - bide th' encounter of assailing eyes\,\n\nNor ope her lap to saint-seducing - gold:\n\nO! she is rich in beauty\; only poor\,\n\nThat when she dies with - beauty dies her store.\n\nBen. Then she hath sworn\, that she will still l - ive chaste?\n\nRom. She hath\, and in that sparing makes huge waste\;\n\nFo - r beauty\, starv'd with her severity\,\n\nCuts beauty off from all posterit - y.\n\nShe is too fair\, too wise\; wisely too fair\,\n\nTo merit bliss by m - aking me despair:\n\nShe hath forsworn to love\, and in that vow\n\nDo I li - ve dead\, that live it to tell it now.\n\nBen. Be rul'd by me\; forget to t - hink of her.\n\nRom. O! teach me how I should forget to think.\n\nBen. By g - iving liberty unto thine eyes:\n\nExamine other beauties.\n\nRom. 'Tis the - way\n\nTo call her's\, exquisite\, in question more.\n\nThese happy masks\, - that kiss fair ladies' brows\,\n\nBeing black\, put us in mind they hide t - he fair:\n\nHe\, that is stricken blind\, cannot forget\n\nThe precious tre - asure of his eyesight lost.\n\nShow me a mistress that is passing fair\,\n\ - nWhat doth her beauty serve\, but as a note\n\nWhere I may read who pass'd - that passing fair?\n\nFarewell: thou canst not teach me to forget.\n\nBen. - I'll pay that doctrine\, or else die in debt. [Exeunt.\n\n\n\n\n\nAn image - should appear at this position in the text.\n\nIf you are able to provide i - t\, see Wikisource:Image guidelines and Help:Adding images for guidance.\n\ - n\n\n(Verona.)\n\n\n\n\n\n​ Scene II.—A Street.\n\nEnter Capulet\, Paris\, - and Servant.\n\n\n\nCap. But Montague is bound as well as I\,\n\nIn penalty - alike\; and 'tis not hard\, I think\,\n\nFor men so old as we to keep the - peace.\n\nPar. Of honourable reckoning are you both\;\n\nAnd pity 'tis\, yo - u liv'd at odds so long.\n\nBut now\, my lord\, what say you to my suit?\n\ - nCap. But saying o'er what I have said before\;\n\nMy child is yet a strang - er in the world\,\n\nShe hath not seen the change of fourteen years:\n\nLet - two more summers wither in their pride\,\n\nEre we may think her ripe to b - e bride.\n\nPar. Younger than she are happy mothers made.\n\nCap. And too s - oon marr'd are those so early made.\n\nEarth hath swallowed all my hopes bu - t she\,\n\nShe is the hopeful lady of my earth:\n\nBut woo her\, gentle Par - is\, get her heart\,\n\nMy will to her consent is but a part\;\n\nAn she ag - ree\, within her scope of choice\n\nLies my consent and fair according voic - e.\n\nThis night I hold an old accustom'd feast\,\n\nWhereto I have invited - many a guest\,\n\nSuch as I love\; and you among the store\,\n\nOne more m - ost welcome\, makes my number more.\n\nAt my poor house look to behold this - night\n\nEarth-treading stars\, that make dark heaven light:\n\nSuch comfo - rt\, as do lusty young men feel\,\n\nWhen well-apparel'd April on the heel\ - n\nOf limping winter treads\, even such delight\n\nAmong fresh female buds - shall you see this night\n\nInherit at my house: hear all\, all see\,\n\nAn - d like her most\, whose merit most shall be:\n\nWhich\, on more view of man - y\, mine being one\,\n\nMay stand in number\, though in reckoning none-\n\n - Come\, go with me—Go\, sirrah\, trudge about\n\nThrough fair Verona\; find - those persons out\,\n\n\n\nWhose names are written there\, and to give them - say\, [Giving a paper.\n\nMy house and welcome on their pleasure stay. [Ex - eunt Capulet and Paris.\n\nServ. Find them out\, whose names are written he - re? It is written\, that the shoemaker should meddle with his yard\, and th - e tailor with his last\, the fisher with his pencil\, and the painter with - his nets\; but I am sent to find those persons\, whose names are here writ\ - , and can never find what names the writing person hath here writ. I must t - o the learned:—in good time.\n\n\n\n\n\nEnter Benvolio and Romeo.\n\n\n\nBe - n. Tut\, man! one fire burns out another's burning\,\n\n⁠One pain lessen'd - by another's anguish\;\n\nTurn giddy\, and be holp by backward turning\;\n\ - n⁠One desperate grief cures with another's languish:\n\nTake thou some new - infection to thy eye\,\n\nAnd the rank poison of the old will die.\n\nRom. - Your plantain leaf is excellent for that.\n\nBen. For what\, I pray thee?\n - \nRom. For your broken shin.\n\nBen. Why\, Romeo\, are thou mad?\n\nRom. No - t mad\, but bound more than a madman is:\n\nShut up in prison\, kept withou - t my food\,\n\nWhipp'd and tormented\, and—Good-den\, good fellow.\n\nServ. - God gi' good den.—I pray\, sir\, can you read?\n\nRom. Ay\, mine own fortu - ne in my misery.\n\nServ. Perhaps you have learn'd it without book\; but I - pray\, can you read anything you see?\n\nRom. Ay\, if I know the letters\, - and the language.\n\nServ. Ye say honestly. Rest you merry.\n\nRom. Stay\, - fellow\; I can read. [Reads.\n\n"Signior Martino\, and his wife\, and daugh - ters\; County Anselme\, and his beauteous sisters\; the lady widow of Vitru - vio\; Signior Placentio\, and his lovely nieces\; Mercutio\, and his brothe - r Valentine\; mine uncle Capulet\, his wife\, and daughters\; my fair niece - Rosaline\; Livia\; Signior Valentio\, and his cousin Tybalt\; Lucio\, and - the lively Helena."\n\nA fair assembly\; whither should they come?\n\nServ. - Up.\n\nRom. Whither? to supper?\n\nServ. To our house.\n\nRom. Whose house - ?\n\nServ. My master's.\n\nRom. Indeed\, I should have asked you that befor - e.\n\nServ. Now\, I'll tell you without asking. My master is the great rich - Capulet\; and if you be not of the house of Montagues\, I pray\, come and - crush a cup of wine. Rest you merry. [Exit.\n\nBen. At this same ancient fe - ast of Capulet's\n\nSups the fair Rosaline\, whom thou so lov'st\,\n\nWith - all the admired beauties of Verona:\n\nGo thither\; and\, with unattainted - eye\,\n\nCompare her face with some that I shall show\,\n\nAnd I will make - thee think thy swan a crow.\n\nRom. When the devout religion of mine eye\n\ - n⁠Maintains such falsehood\, then turns tears to fires\;\n\nAnd these\, who - \, often drown'd\, could never die\,\n\n⁠Transparent heretics\, be burnt fo - r liars.\n\nOne fairer than my love! the all-seeing sun\n\nNe'er saw her ma - tch\, since first the world begun.\n\nBen. Tut! you saw her fair\, none els - e being by\,\n\nHerself pois'd with herself in either eye\;\n\nBut in those - crystal scales\, let there be weigh'd\n\nYour lady's love against some oth - er maid\,\n\nThat I will show you shining at this feast\,\n\nAnd she shall - scant show well\, that now shows best.\n\nRom. I'll go along\, no such sigh - t to be shown\,\n\n\n\nBut to rejoice in splendour of mine own. [Exeunt.\n\ - n\n\n\n\nScene III—A Room in Capulet's House.\n\nEnter Lady Capulet and Nur - se.\n\n\n\nLa. Cap. Nurse\, where's my daughter? call her forth to me.\n\nN - urse. Now\, by my maiden-head at twelve year old\,\n\nI bade her come—What\ - , lamb! what\, lady-bird!—\n\nGod forbid!—where's this girl?—what\, Juliet! - \n\n\n\n\n\nEnter Juliet.\n\n\n\nJul. How now! who calls?\n\nNurse. Your mo - ther.\n\nJul. Madam\, I am here.\n\nWhat is your will?\n\nLa. Cap. This is - the matter.—Nurse\, give leave awhile\,\n\nWe must talk in secret.—Nurse\, - come back again:\n\nI have remember'd me\, thou shalt hear our counsel.\n\n - Thou know'st my daughter's of a pretty age.\n\nNurse. 'Faith\, I can tell h - er age unto an hour.\n\nLa. Cap. She's not fourteen.\n\nNurse. I'll lay fou - rteen on my teeth.\n\nAnd yet to my teen be it spoken I have but four\,\n\n - She is not fourteen. How long is it now\n\nTo Lammas-tide?\n\n\n\n​La. Cap. - A fortnight\, and odd days.\n\nNurse. Even or odd\, of all days in the yea - r\,\n\nCome Lammas-eve at night shall she be fourteen.\n\nSusan and she\,—G - od rest all Christian souls!—\n\nWere of an age.—Well\, Susan is with God\; - \n\nShe was too good for me. But\, as I said\,\n\nOn Lammas-eve at night sh - all she be fourteen\;\n\nThat shall she\, marry: I remember it well.\n\n'Ti - s since the earthquake now eleven years\;\n\nAnd she was wean'd\,—I never s - hall forget it\,—\n\nOf all the days of the year\, upon that day\;\n\nFor I - had then laid wormwood to my dug\,\n\nSitting in the sun under the dove-ho - use wall:\n\nMy lord and you were then at Mantua.—\n\nNay\, I do bear a bra - in:—but\, as I said\,\n\nWhen it did taste the wormwood on the nipple\n\nOf - my dug\, and felt it bitter\, pretty fool\,\n\nTo see it tetchy\, and fall - out with the dug!\n\nShake\, quoth the dove-house: 'twas no need\, I trow\ - ,\n\nTo bid me trudge.\n\nAnd since that time it is eleven years\;\n\nFor t - hen she could stand alone\; nay\, by the rood\,\n\nShe could have run and w - addled all about\,\n\nFor even the day before she broke her brow:\n\nAnd th - en my husband—God be with his soul!\n\n'A was a merry man\,—took up the chi - ld:\n\n"Yea\," quoth he\, "dost thou fall upon thy face?\n\n\n\nThou wilt f - all backward\, when thou hast more wit\;\n\nAn image should appear at this - position in the text.\n\nIf you are able to provide it\, see Wikisource:Ima - ge guidelines and Help:Adding images for guidance.\n\n\n\n\n\nWilt thou not - \, Jule?" and\, by my holy-dam\,\n\nThe pretty wretch left crying\, and sai - d—"Ay."\n\nTo see\, now\, how a jest shall come about!\n\nI warrant\, an I - should live a thousand years.\n\nI never should forget it: "Wilt thou not\, - Jule?" quoth he:\n\nAnd\, pretty fool\, it stinted\, and said—"Ay."\n\nLa. - Cap. Enough of this: I pray thee: hold they peace.\n\nNurse. Yes\, madam. - Yet I cannot choose but laugh\,\n\nTo think it should leave crying\, and sa - y—"Ay:"\n\nAnd yet\, I warrant\, it had upon its brow\n\nA bump as big as a - young cockerel's stone\,\n\nA perilous knock\; and it cried bitterly.\n\n" - Yea\," quoth my husband\, "fall'st upon thy face?\n\nThou wilt fall backwar - d\, when thou com'st to age\;\n\nWilt thou not\, Jule?" it stinted\, and sa - id—"Ay."\n\nJul. And stint thou too\, I pray thee\, nurse\, say I.\n\nNurse - . Peace\, I have done. God mark thee to his grace!\n\nThou was the pretties - t babe that e'er I nurs'd:\n\nAn I might live to see thee married once\,\n\ - nI have my wish.\n\n\n\n​La. Cap. Marry\, that marry is the very theme\n\nI - came to talk of:—tell me\, daughter Juliet\,\n\nHow stands your dispositio - n to be married?\n\nJul. It is an honour that I dream not of.\n\nNurse. An - honour! were not I thine only nurse\,\n\nI would say\, thou hadst sucked wi - sdom from thy teat.\n\nLa. Cap. Well\, think of marriage now\; younger than - you\,\n\nHere in Verona\, ladies of esteem\,\n\nAre made already mothers: - by my count\,\n\nI was your mother\, much upon these years\n\nThat you are - now a maid. Thus\, then\, in brief\;—\n\nThe valiant Paris seeks you for hi - s love.\n\nNurse. A man\, young lady! lady\, such a man\,\n\nAs all the wor - ld—Why\, he's a man of wax.\n\nLa. Cap. Verona's summer hath not such a flo - wer.\n\nNurse. Nay\, he's a flower\; in faith\, a very flower.\n\nLa. Cap. - What say you? can you love the gentleman?\n\nThis night you shall behold hi - m at out' feast:\n\nRead o'er the volume of young Paris' face\,\n\nAnd find - delight writ there with beauty's pen.\n\nExamine every married lineament\, - \n\nAnd see how one another lends content\;\n\nAnd what obscur'd in this fa - ir volume lies\,\n\nFind written in the margin of his eyes.\n\nThis preciou - s book of love\, this unbound lover\,\n\nTo beautify him\, only lacks a cov - er:\n\nThe fish lives in the sea\; and 'tis much pride\,\n\nFor fair withou - t the fair within to hide.\n\nThat book in many's eyes doth share the glory - \,\n\nThat in gold clasps locks in the golden story\;\n\nSo shall you share - all that he doth possess\,\n\nBy having him making yourself no less.\n\nNu - rse. No less? nay\, bigger: women grow by men.\n\nLa. Cap. Speak briefly\, - can you like of Paris' love?\n\nJul. I'll look to like\, if looking liking - move\;\n\nBut no more deep will I endart mine eye\,\n\nThan your consent gi - ves strength to make it fly.\n\nEnter a Servant.\n\n\n\nServ. Madam\, the g - uests are come\, supper served up\, you called\, my young lady asked for\, - the nurse cursed in the pantry\, and every thing in extremity. I must hence - to wait\; I beseech you\, follow straight.\n\nLa. Cap. We follow thee. Jul - iet\, the county stays.\n\nNurse. Go\, girl\, seek happy nights to happy da - ys. [Exeunt.\n\n\n\n\n\nScene IV.—A Street.\n\nEnter Romeo\, Mercutio\, Ben - volio\, with five or six Maskers\, Torch-bearers\, and others.\n\n\n\nRom. - What\, shall this speech be spoke for our excuse\,\n\nOr shall we on withou - t apology?\n\nBen. The date is out of such prolixity:\n\nWe'll have no Cupi - d hood-wink'd with a scarf\,\n\nBearing a Tartar's painted bow of lath\,\n\ - nScaring the ladies like a crow-keeper\;\n\nNor no without-book prologue\, - faintly spoke\n\nAfter the prompter\, for our entrance:\n\nBut\, let them m - easure us by what they will\,\n\nWe'll measure them a measure\, and be gone - .\n\nRom. Give me a torch\; I am not for this ambling:\n\nBeing but heavy\, - I will bear the light.\n\nMer. Nay\, gentle Romeo\, we must have you dance - .\n\nRom. Not I\, believe me. You have dancing shoes\,\n\nWith nimble soles - \; I have a soul of lead\,\n\nSo stakes me to the ground\, I cannot move.\n - \nMer. You are a lover: borrow Cupid's wings\,\n\nAnd soar with them above - a common bound.\n\nRom. I am too sore enpierced with his shaft\,\n\nTo soar - with his light feathers\; and so bound\,\n\nI cannot bound a pitch above d - ull woe:\n\nUnder love's heavy burden do I sink.\n\nMer. And\, to sink in i - t\, should you burden love\;\n\nToo great oppression for a tender thing.\n\ - nRom. Is love a tender thing? it is too rough\,\n\nToo rude\, too boisterou - s\; and it pricks like thorn.\n\nMer. If love be rough with you\, be rough - with love\;\n\nPrick love for pricking\, and you beat love down.—\n\n\n\nGi - ve me a case to put my visage in: [Putting on a mask.\n\nA visor for a viso - r!—what care I\,\n\nWhat curious eye doth quote deformities?\n\nHere are th - e beetle-brows shall blush for me.\n\nBen. Come\, knock\, and enter\; and n - o sooner in\,\n\nBut every man betake him to his legs.\n\nRom. A torch for - me: let wantons\, light of heart\,\n\nTickle the senseless rushes with thei - r heels\;\n\nFor I am proverb'd with a grandsire phrase\,—\n\nI'll be a can - dle-holder\, and look on:\n\nThe game was ne'er so fair\, and I am done.\n\ - nMer. Tut! dun's the mouse\, the constable's own word.\n\nIf thou art dun\, - we'll draw thee from the mire\n\nOf this save-reverence love\, wherein tho - u stick'st\n\nUp to the ears.—Come\, we burn day-light\, ho.\n\nRom. Nay\, - that's not so.\n\nMer. I mean\, sir\, in delay\n\nWe waste our lights in va - in\, like lamps by day.\n\nTake our good meaning\, for our judgment sits\n\ - nFive times in that\, ere once in our five wits.\n\nRom. And we mean well i - n going to this mask\,\n\nBut 'tis no wit to go.\n\nMer. Why\, may one ask? - \n\nRom. I dreamt a dream to-night?\n\nMer. And so did I.\n\nRom. Well\, wh - at was yours?\n\nMer. That dreamers often lie.\n\nRom. In bed asleep\, whil - e they do dream things true.\n\nMer. O! then\, I see\, queen Mab hath been - with you.\n\nShe is the fairies' midwife\; and she comes\n\nIn shape no big - ger than an agate-stone\n\nOn the fore-finger of an alderman\,\n\nDrawn wit - h a team of little atomies\n\nOver men's noses as they lie asleep:\n\nHer w - aggon-spokes made of long spinners' legs\;\n\nThe cover\, of the wings of g - rasshoppers\;\n\nThe traces\, of the smallest spider's web\;\n\nThe collars - \, of the moonshine's watery beams:\n\nHer whip\, of cricket's bone\; the l - ash\, of film:\n\nHer waggoner\, a small grey-coated gnat\,\n\nNot half so - big as a round little worm\n\nPrick'd from the lazy finger of a maid.\n\nHe - r chariot is an empty hazel-nut\,\n\nMade by the joiner squirrel\, or old g - rub\,\n\nTime out of mind the fairies' coach-makers.\n\nAnd in this state s - he gallops night by night\n\nThrough lovers' brains\, and then they dream o - f love: ​\n\nOn courtiers' knees\, that dream on court'sies straight:\n\nO' - er lawyers' fingers\, who straight dream on fees:\n\nO'er ladies' lips\, wh - o straight on kisses dream\;\n\nWhich oft the angry Mab with blisters plagu - es\,\n\nBecause their breaths with sweet-meats tainted are.\n\nSometime she - gallops o'er a courtier's nose\,\n\nAnd then dreams he of smelling out a s - uit:\n\nAnd sometime comes she with a tithe-pig's tail\,\n\nTickling a pars - on's nose as 'a lies asleep\;\n\nThen he dreams of another benefice.\n\nSom - etime she driveth o'er a soldier's neck\,\n\nAnd then dreams he of cutting - foreign throats\,\n\nOf breaches\, ambuscadoes\, Spanish blades\,\n\nOf hea - lths five fathom deep\; and then anon\n\nDrums in his ear\, at which he sta - rts\, and wakes\;\n\nAnd\, being thus frighted\, swears a prayer or two\,\n - \nAnd sleeps again. This is that very Mab\,\n\nThat plats the manes of hors - es in the night\;\n\nAnd bakes the elf-locks in foul sluttish hairs\,\n\nWh - ich\, once untangled\, much misfortune bodes.\n\nThis is the hag\, when mai - ds lie on their backs\,\n\nThat presses them\, and learns them first to bea - r\,\n\nMaking them women of good carriage.\n\nThis\, is she—\n\nRom. Peace\ - , peace! Mercutio\, peace!\n\nThou talk'st of nothing.\n\nMer. True\, I tal - k of dreams\,\n\nWhich are the children of an idle brain\,\n\nBegot of noth - ing but vain fantasy\;\n\nWhich is as thin of substance as the air\;\n\nAnd - more inconstant than the wind\, who woos\n\nEven now the frozen bosom of t - he north\,\n\nAnd\, being anger'd\, puffs away from thence\,\n\nTurning his - face to the dew-dropping south.\n\nBen. This wind\, you talk of\, blows us - from ourselves\;\n\nSupper is done\, and we shall come too late.\n\nRom. I - fear\, too early\; for my mind misgives\,\n\nSome consequence\, yet hangin - g in the stars\,\n\nShall bitterly begin his fearful date\n\nWith this nigh - t's revels\; and expire the term\n\nOf a despised life\, clos'd in my breas - t\,\n\nBy some vile forfeit of untimely death:\n\nBut He\, that hath the st - eerage of my course\,\n\nDirect my sail.—On\, lusty gentlemen.\n\nBen. Stri - ke\, drum. [Exeunt\n\n\n\n\n\nAn image should appear at this position in th - e text.\n\nIf you are able to provide it\, see Wikisource:Image guidelines - and Help:Adding images for guidance.\n\n\n\n('Court-cupboard\,' and Plate.) - \n\n\n\n\n\nScene V.—A Hall in Capulet's House.\n\nMusicians waiting. Enter - Servants.\n\n\n\n1 Serv. Where's Potpan\, that he helps not to take away? - he shift a trencher! he scrape a trencher!\n\n2 Serv. When good manners sha - ll lie all in one or two men's hands\, and they unwashed too\, 'tis a foul - thing.\n\n1 Serv. Away with the joint-stools\, remove the court-cupboard\, - look to the plate.—Good thou\, save ​me a piece of marchpane\; and\, as tho - u lovest me\, let the porter let in Susan Grindstone\, and Nell.—Antony! an - d Potpan!\n\n2 Serv. Ay\, boy\; ready.\n\n1 Serv. You are looked for\, and - called for\, asked for\, and sought for\, in the great chamber.\n\n2 Serv. - We cannot be here and there too.—Cheerly\, boys: be brisk awhile\, and the - longer liver take all. [They retire behind.\n\nEnter Capulet\, &c.\, with t - he Guests\, and the Maskers.\n\n\n\nCap. Welcome\, gentlemen! Ladies that h - ave their toes\n\nUnplagu'd with corns\, will have a bout with you:—\n\nAh - ha\, my mistresses! which of you all\n\nWill now deny to dance? she that ma - kes dainty\, she\,\n\nI'll swear\, hath corns. Am I come near you now?\n\nY - ou are welcome\, gentlemen! I have seen the day\,\n\nThat I have worn a vis - or\, and could tell\n\nA whispering tale in a fair lady's ear\,\n\nSuch as - would please:—'tis gone\, 'tis gone\, 'tis gone.\n\nYou are welcome\, gentl - emen!—Come\, musicians\, play.\n\n\n\nA hall! a hall! give room\, and foot - it\, girls. [Music plays\, and they dance.\n\nMore light\, ye knaves! and t - urn the tables up\,\n\nAnd quench the fire\, the room is grown too hot.—\n\ - nAh! sirrah\, this unlook'd-for sport comes well.\n\nNay\, sit\, nay\, sit\ - , good cousin Capulet\,\n\nFor you and I are past our dancing days:\n\nHow - long is't now\, since last yourself and I\n\nWere in a mask?\n\n2 Cap. By'r - lady\, thirty years.\n\n1 Cap. What\, man! 'tis not so much\, 'tis not so - much:\n\n'Tis since the nuptial of Lucentio\,\n\nCome Pentecost as quickly - as it will\,\n\nSome five and twenty years\; and then we mask'd.\n\n2 Cap. - 'Tis more\, 'tis more: his son is elder\, sir\;\n\nHis son is thirty.\n\n1 - Cap. Will you tell me that?\n\nHis son was but a ward two years ago.\n\nRom - . What lady is that\, which doth enrich the hand\n\nOf yonder knight?\n\nSe - rv. I know not\, sir.\n\nRom. O! she doth teach the torches to burn bright. - \n\nHer beauty hangs upon the cheek of night\n\nLike a rich jewel in an Æth - iop's ear\;\n\nBeauty too rich for use\, for earth too dear!\n\nSo shows a - snowy dove trooping with crows\,\n\nAs yonder lady o'er her fellows shows.\ - n\nThe measure done\, I'll watch her place of stand\,\n\nAnd\, touching her - s\, make blessed my rude hand.\n\nDid my heart love till now? forswear it\, - sight!\n\nI never saw true beauty till this night.\n\nTyb. This\, by his v - oice\, should be a Montague.—\n\nFetch me my rapier\, boy.—What! dares the - slave\n\nCome hither\, cover'd with an antic face\,\n\nTo fleer and scorn a - t our solemnity?\n\nNow\, by the stock and honour of my kin\,\n\nTo strike - him dead I hold it not a sin.\n\n1 Cap. Why\, how now\, kinsman! wherefore - storm you so?\n\nTyb. Uncle\, this is a Montague\, our foe\;\n\nA villain\, - that is hither come in spite\,\n\nTo scorn at our solemnity this night.\n\ - n1 Cap. Young Romeo is it?\n\nTyb. 'Tis he\, that villain Romeo.\n\n1 Cap. - Content thee\, gentle coz\, let him alone\,\n\nHe bears him like a portly g - entleman\;\n\nAnd\, to say truth\, Verona brags of him\,\n\nTo be a virtuou - s and well-govern'd youth.\n\nI would not for the wealth of all this town\, - \n\nHere\, in my house\, do him disparagement\;\n\nTherefore\, be patient\, - take no note of him:\n\nIt is my will\; the which if thou respect\,\n\nSho - w a fair presence\, and put off these frowns\,\n\nAn ill-beseeming semblanc - e for a feast.\n\nTyb. It fits\, when such a villain is a guest.\n\nI'll no - t endure him.\n\n1 Cap. He shall be endur'd:\n\nWhat! goodman boy!—I say\, - he shall\;—go to\;—\n\nAm I the master here\, or you? go to.\n\nYou'll not - endure him! God shall mend my soul—\n\nYou'll make a mutiny among my guests - .\n\nYou will set cock-a-hoop! you'll be the man!\n\nTyb. Why\, uncle\, 'ti - s a shame.\n\n1 Cap. Go to\, go to\;\n\nYou are a saucy boy.—Is't so\, inde - ed?—\n\nThis trick may chance to scath you\;—I know what.\n\nYou must contr - ary me! marry\, 'tis time—\n\nWell said\, my hearts!—You are a princox\; go - :—\n\nBe quiet\, or—More light\, more light!—for shame!\n\nI'll make you qu - iet\;—What!—Cheerly\, my hearts!\n\nTyb. Patience perforce with wilful chol - er meeting\,\n\nMakes my flesh tremble in their different greeting.\n\nI wi - ll withdraw: but this intrusion shall\,\n\n\n\nNow seeming sweet\, convert - to bitter gall. [Exit.\n\nRom. If I profane with my unworthiest hand [To Ju - liet.\n\n⁠This holy shrine\, the gentle fine is this\,—\n\nMy lips\, two bl - ushing pilgrims\, ready stand\n\n⁠To smooth that rough touch with a tender - kiss.\n\nJul. Good pilgrim\, you do wrong your hand too much\,\n\n⁠Which ma - nnerly devotion shows in this\;\n\nFor saints have hands that pilgrims' han - ds do touch\,\n\n⁠And palm to palm is holy palmers' kiss.\n\nRom. Have not - saints lips\, and holy palmers too?\n\nJul. Ay\, pilgrim\, lips that they m - ust use in prayer.\n\nRom. O! then\, dear saint\, let lips do what hands do - \;\n\nThey pray\, grant thou\, lest faith turn to despair.\n\nJul. Saints d - o not move\, though grant for prayers' sake.\n\nRom. Then move not\, while - my prayer's effect I take.\n\n\n\nThus from my lips\, by thine\, my sin is - purg'd. [Kissing her.\n\nJul. Then have my lips the sin that they have took - .\n\nRom. Sin from my lips? O\, trespass sweetly urg'd!\n\nGive me my sin a - gain.\n\nJul. You kiss by the book.\n\nNurse. Madam\, your mother craves a - word with you.\n\nRom. What is her mother?\n\nNurse. Marry\, bachelor\,\n\n - Her mother is the lady of the house\,\n\nAnd a good lady\, and a wise\, and - virtuous.\n\nI nurs'd her daughter\, that you talk'd withal\;\n\nI tell yo - u—he that can lay hold of her\n\nShall have the chinks.\n\n\n\n​Rom. Is she - a Capulet?\n\nO\, dear account! my life is my foe's debt.\n\nBen. Away\, b - egone: the sport is at the best.\n\nRom. Ay\, so I fear\; the more is my un - rest.\n\n1 Cap. Nay\, gentlemen\, prepare not to be gone\;\n\nWe have a tri - fling foolish banquet towards.—\n\nIs it e'en so? Why then\, I thank you al - l\;\n\nI thank you\, honest gentlemen\; good night:—\n\nMore torches here!— - Come on\, then let's to bed.\n\nAh\, sirrah\, by my fay\, it waxes late\;\n - \n\n\nI'll to my rest. [Exeunt all but Juliet and Nurse.\n\nJul. Come hithe - r\, nurse. What is yond' gentleman?\n\nNurse. The son and heir of old Tiber - io.\n\nJul. What's he\, that now is going out of door?\n\nNurse. Marry\, th - at\, I think\, be young Petruchio.\n\nJul. What's he\, that follows here\, - that would not dance?\n\nNurse. I know not.\n\nJul. Go\, ask his name.—If h - e be married\,\n\nMy grave is like to be my wedding bed.\n\nNurse. His name - is Romeo\, and a Montague\;\n\nThe only son of your great enemy.\n\nJul. M - y only love sprung from my only hate!\n\nToo early seen unknown\, and known - too late!\n\nProdigious birth of love it is to me\,\n\nThat I must love a - loathed enemy.\n\nNurse. What's this? what's this?\n\nJul. A rhyme I learn' - d even now\n\nOf one I danc'd withal. [One calls within\, Juliet!\n\nNurse. - Anon\, anon:\n\nCome\, let's away\; the strangers all are gone. [Exeunt.\n - \n\n\n\n\nEnter Chorus.\n\n\n\nNow old desire doth in his death-bed lie\,\n - \nAnd young affection gapes to be his heir:\n\nThat fair\, for which love g - roan'd for\, and would die\,\n\nWith tender Juliet match'd is now not fair. - \n\nNow Romeo is belov'd\, and loves again\,\n\nAlike bewitched by the char - m of looks\;\n\nBut to his foe suppos'd he must complain\,\n\nAnd she steal - love's sweet bait from fearful hooks:\n\nBeing held a foe\, he may not hav - e access\n\nTo breathe such vows as lovers use to swear\;\n\nAnd she as muc - h in love\, her means much less\n\nTo meet her new-beloved anywhere:\n\nBut - passion lends them power\, time\, means\, to meet\,\n\n\n\nTempering extre - mities with extreme sweet. [Exit.\n\n\n\n\n\nAn image should appear at this - position in the text.\n\nIf you are able to provide it\, see Wikisource:Im - age guidelines and Help:Adding images for guidance.\n\n\n\n\n\nAbout this d - igital edition\n\n\nThis e-book comes from the online library Wikisource[1] - . This multilingual digital library\, built by volunteers\, is committed to - developing a free accessible collection of publications of every kind: nov - els\, poems\, magazines\, letters...\n\nWe distribute our books for free\, - starting from works not copyrighted or published under a free license. You - are free to use our e-books for any purpose (including commercial exploitat - ion)\, under the terms of the Creative Commons Attribution-ShareAlike 3.0 U - nported[2] license or\, at your choice\, those of the GNU FDL[3].\n\nWikiso - urce is constantly looking for new members. During the realization of this - book\, it's possible that we made some errors. You can report them at this - page[4].\n\nThe following users contributed to this book:\n\nAngelprincess7 - 2\n\nDjr13\n\nThomasBot\n\nBirgitteSB\n\nMpaa\n\nBeleg Tâl\n\nEinstein95\n\ - nKathleen.wright5\n\nEncycloPetey\n\nDariyman\n\n\n\n\n\n* * *\n\n\n\n↑ htt - p://wikisource.org\n\n↑ http://www.creativecommons.org/licenses/by-sa/3.0\n - \n↑ http://www.gnu.org/copyleft/fdl.html\n\n↑ http://wikisource.org/wiki/Wi - kisource:Scriptorium\n\n\n\n\n\n -STATUS:CONFIRMED -DTSTART;TZID=Europe/Berlin:20200221T180000 -DTEND;TZID=Europe/Berlin:20200221T190000 -END:VEVENT -BEGIN:VTIMEZONE -TZID:Europe/Berlin -X-LIC-LOCATION:Europe/Berlin -BEGIN:DAYLIGHT -TZOFFSETFROM:+0100 -TZOFFSETTO:+0200 -TZNAME:CEST -DTSTART:19700329T020000 -RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU -END:DAYLIGHT -BEGIN:STANDARD -TZOFFSETFROM:+0200 -TZOFFSETTO:+0100 -TZNAME:CET -DTSTART:19701025T030000 -RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU -END:STANDARD -END:VTIMEZONE -END:VCALENDAR \ No newline at end of file diff --git a/tests/fixtures/Romeo-and-Juliet.txt b/tests/fixtures/Romeo-and-Juliet.txt deleted file mode 100644 index 9a30625a..00000000 --- a/tests/fixtures/Romeo-and-Juliet.txt +++ /dev/null @@ -1,1746 +0,0 @@ -Romeo and Juliet (The Illustrated Shakespeare) - - - William Shakespeare - - - - - -1847 - - - - - -Exported from Wikisource on 02/23/20 - - - - - -This work may need to be standardized using Wikisource's style guidelines. - -If you'd like to help, please review the help pages. - - - - - -PROLOGUE - - -​ PROLOGUE - - - -CHORUS. - - - -Two households, both alike in dignity, - -In fair Verona, where we lay our scene, - -From ancient grudge break to new mutiny, - -Where civil blood makes civil hands unclean. - -From forth the fatal loins of these two foes - -A pair of star-cross'd lovers take their life; - -Whose misadventur'd piteous overthrows - -Do, with their death, bury their parents' strife. - -The fearful passage of their death-mark'd love, - -And the continuance of their parents' rage, - -Which, but their children's end, nought could remove, - -Is now the two hours' traffic of our stage; - -The which if you with patient ears attend, - -What here shall miss, our toil shall strive to mend. - - - - - -An image should appear at this position in the text. - -If you are able to provide it, see Wikisource:Image guidelines and Help:Adding images for guidance. - - - - - -ACT I - - -​ - - - -Scene I.—A Public Place. - -Enter Sampson and Gregory, armed with Swords and Bucklers. - - - -Sam. Gregory, on my word, we'll not carry coals. - -Gre. No, for then we should be colliers. - -Sam. I mean, an we be in choler, we'll draw. - -Gre. Ay, while you live, draw your neck out of the collar. - -Sam. I strike quickly, being moved. - -Gre. But thou art not quickly moved to strike. - -Sam. A dog of the house of Montague moves me. - -Gre. To move is to stir, and to be valiant is to stand: therefore, if thou art moved, thou run'st away. - -Sam. A dog of that house shall move me to stand. I will take the wall of any man or maid of Montague's. - -Gre. That shows thee a weak slave; for the weakest goes to the wall. - -Sam. 'Tis true; and therefore women, being the weaker vessels,are ever thrust to the wall:—therefore, I will push Montague's men from the wall, and thrust his maids to the wall. - -Gre. The quarrel is between our masters, and us their men. - -Sam. 'Tis all one, I will show myself a tyrant: when I have fought with the men, I will be civil with the maids; I will cut off their heads. - -Gre. The heads of the maids? - -Sam. Ay, the heads of the maids, or their maidenheads; take it in what sense thou wilt. - -Gre. They must take it in sense, that feel it. - -Sam. Me they shall feel, while I am able to stand; and 'tis known, I am a pretty piece of flesh. - -Gre. 'Tis well, thou art not fish; if thou hadst, ​thou hadst been poor John. Draw thy tool; here comes two of the house of the Montagues. - - - - - -Enter Abraham and Balthasar. - - - -Sam. My naked weapon is out: quarrel, I will back thee. - -Gre. How! turn thy back, and run? - -Sam. Fear me not. - -Gre. No marry: I fear thee! - -Sam. Let us take the law of our sides; let them begin. - -Gre. I will frown as I pass by, and let them take it as they list. - -Sam. Nay, as they dare. I will bite my thumb at them; which is a disgrace to them, if they bear it. - -Abr. Do you bite your thumb at us, sir? - -Sam. I do bite my thumb, sir. - -Abr. Do you bite your thumb at us, sir? - - - -An image should appear at this position in the text. - -If you are able to provide it, see Wikisource:Image guidelines and Help:Adding images for guidance. - - - -Sam. Is the law of our side, if I say—ay? - -Gre. No. - -Sam. No, sir, I do not bite my thumb at you, sir; but I bite my thumb, sir. - -Gre. Do you quarrel, sir? - -Abr. Quarrel, sir? no, sir. - -Sam. If you do, sir, I am for you: I serve as good a man as you. - -Abr. No better. - -Sam. Well, sir. - - - - - -Enter Benvolio, at a distance. - - - -Gre. Say—better: here comes one of my master's kinsmen. - -Sam. Yes, better, sir. - -Abr. You lie. - -Sam. Draw, if you be men.—Gregory, remember thy swashing blow. [They fight. - -Ben. Part, fools! put up your swords; you know not what you do. [Beats down their Swords. - - - - - -Enter Tybalt. - - - -Tyb. What! art thou drawn among these heartless hinds? - -Turn thee, Benvolio, look upon thy death. - -Ben. I do but keep the peace: put up thy sword, - -Or manage it to part these men with me. - -Tyb. What! drawn, and talk of peace? I hate the word, - -As I hate hell, all Montagues, and thee. - - - -Have at thee, coward. [They fight. - -​ Enter several persons of both Houses, who join the fray; then enter Citizens, with clubs or partisans. - - - -1 Cit. Clubs, bills, and partisans! strike! beat them down! - -Down with the Capulets! down with the Montagues! - - - - - -Enter Capulet in his gown; and Lady Capulet. - - - -Cap. What noise is this?—Give me my long sword, ho! - -La. Cap. A crutch, a crutch!—Why call you for a sword? - -Cap. My sword, I say!—Old Montague is come, - -And flourishes his blade in spite of me. - - - - - -Enter Montague and Lady Montague. - - - -Mon. Thou villain Capulet!—Hold me not; let me go. - -La. Mon. Thou shalt not stir one foot to seek a foe. - - - - - -Enter Prince, with his train. - - - -Prin. Rebellious subjects, enemies to peace, - -Profaners of this neighbour-stained steel— - -Will they not hear?—what ho! you men, you beasts, - -That quench the fire of your pernicious rage - -With purple fountains issuing from your veins, - -On pain of torture, from those bloody hands - -Throw your mis–temper'd weapons to the ground, - -And hear the sentence of your moved prince.— - -Three civil brawls, bred of an airy word, - -By thee, old Capulet, and Montague, - -Have thrice disturb'd the quiet of our streets; - -And made Verona's ancient citizens - -Cast by their grave beseeming ornaments, - -To wield old partisans, in hands as old, - -Canker'd with peace, to part your canker'd hate. - -If ever you disturb our streets again, - -Your lives shall pay the forfeit of the peace: - -For this time, all the rest depart away. - -You Capulet, shall go along with me; - -And, Montague, come you this afternoon, - -To know our further pleasure in this case, - -To old Free-town, our common judgment-place. - - - -Once more, on pain of death, all men depart. [Exeunt Prince and Attendants; Capulet, Lady Capulet, Tybalt, Citizens, and Servants. - -Mon. Who set this ancient quarrel new abroach? - -Speak, nephew, were you by when it began? - -Ben. Here were the servants of your adversary, - -And yours, close fighting ere I did approach. - -I drew to part them: in the instant came - -The fiery Tybalt, with his sword prepar'd; - -Which, as he breath'd defiance to my ears, - -He swung about his head, and cut the winds, - -Who, nothing hurt withal, hiss'd him in scorn. - -While we were interchanging thrusts and blows, - -Came more and more, and fought on part and part, - -Till the prince came, who parted either part. - -La. Mon. O! where is Romeo?—saw you him to-day? - -Right glad I am he was not at this fray. - -Ben. Madam, an hour before the worshipp'd sun - -Peer'd forth the golden window of the east, - -A troubled mind drave me to walk abroad; - -Where, underneath the grove of sycamore - -That westward rooteth from the city's side, - -So early walking did I see your son. - -Towards him I made; but he was 'ware of me, - -And stole into the covert of the wood: - -I, measuring his affections by my own, - -Which then most sought, where most might not be found, - -Being one too many by my weary self, - -Pursu'd my humour, not pursuing his, - -And gladly shunn'd who gladly fled from me. - -Mon. Many a morning hath he there been seen, - -With tears augmenting the fresh morning's dew, - -Adding to clouds more clouds with his deep sighs: - -But all so soon as the all-cheering sun - -Should in the furthest east begin to draw - -The shady curtains from Aurora's bed, - -Away from the light steals home my heavy son, - -And private in his chamber pens himself; - -Shuts up his windows, locks fair daylight out, - -And makes himself an artificial night. - -Black and portentous must this humour prove, - -Unless good counsel may the cause remove. - -Ben. My noble uncle, do you know the cause? - -Mon. I neither know it, nor can learn of him. - -Ben. Have you importun'd him by any means? - -Mon. Both by myself, and many other friends: - -But he, his own affections' counsellor, - -Is to himself—I will not say, how true— - -But to himself so secret and so close, - -So far from sounding and discovery, - -As is the bud bit with an envious worm. - -Ere he can spread his sweet leaves to the air, - -Or dedicate his beauty to the sun. - -Could we but learn from whence his sorrows grow, - -We would as willingly give cure, as know. - - - - - -Enter Romeo, at a distance. - - - -Ben. See, where he comes: so please you, step aside; - -I'll know his grievance, or be much denied. - -Mon. I would, thou wert so happy by thy stay, - - - -To hear true shrift.—Come, madam, let's away. [Exeunt Montague and Lady. - -Ben. Good morrow, cousin. - -Rom. Is the day so young? - -Ben. But new struck nine. - -Rom. Ah me! sad hours seem long. - -Was that my father that went hence so fast? - -Ben. It was. What sadness lengthens Romeo's hours? - -Rom. Not having that, which, having, makes them short. - -Ben. In love? - -Rom. Out. - -Ben. Of love? - -Rom. Out of her favour, where I am in love. - -Ben. Alas, that love, so gentle in his view, - -Should be so tyrannous and rough in proof! - -Rom. Alas, that love, whose view is muffled still, - -Should, without eyes, see pathways to his will! - -Where shall we dine?—O me!—What fray was here? - -Yet tell me not, for I have heard it all. - -Here's much to do with hate, but more with love: - -Why then, O brawling love! O loving hate! - -O any thing, of nothing first created! - -O heavy lightness! serious vanity! - -Mis-shapen chaos of well-seeming forms! - -Feather of lead, bright smoke, cold fire, sick health! - -Still-waking sleep, that is not what it is!— - -This love feel I, that feel no love in this. - -Dost thou not laugh? - -Ben. No, coz; I rather weep. - -Rom. Good heart, at what? - - - -​Ben. At thy good heart's oppression. - -Rom. Why, such is love's transgression.- - -Griefs of mine own lie heavy in my breast; - -Which thou wilt propagate, to have it presse'd - -With more of thine: this love, that thou hast shown, - -Doth add more grief to too much of mine own. - -Love is a smoke, made with the fume of sighs; - -Being purg'd, a fire sparkling in lovers' eyes; - -Being vex'd, a sea nourish'd with lover's tears: - -What is it else? a madness most discreet, - -A choking gall, and a preserving sweet. - - - -Farewell, my coz. [Going. - -Ben. Soft, I will go along: - -And if you leave me so, you do me wrong. - -Rom. Tut! I have lost myself: I am not here; - -This is not Romeo, he's some other where. - -Ben. Tell me in sadness, who is it that you love. - -Rom. What! shall I groan, and tell thee? - -Ben Groan! why, no; - -But sadly tell me, who. - -Rom. Bid a sick man in sadness make his will; - -A word ill urg'd to one that is so ill.- - -In sadness, cousin, I do love a woman. - -Ben. I aim'd so near, when I suppos'd you lov'd. - -Rom. A right good mark-man! And she's fair I love. - -Ben. A right fair mark, fair coz, is soonest hit. - -Rom. Well, in that hit, you miss: she'll not be hit. - -With Cupid's arrow. She hath Dian's wit; - -And in strong proof of chastity well arm'd, - -From love's weak childish bow she lives unharm'd - -She will not stay the siege of loving terms, - -Nor bide th' encounter of assailing eyes, - -Nor ope her lap to saint-seducing gold: - -O! she is rich in beauty; only poor, - -That when she dies with beauty dies her store. - -Ben. Then she hath sworn, that she will still live chaste? - -Rom. She hath, and in that sparing makes huge waste; - -For beauty, starv'd with her severity, - -Cuts beauty off from all posterity. - -She is too fair, too wise; wisely too fair, - -To merit bliss by making me despair: - -She hath forsworn to love, and in that vow - -Do I live dead, that live it to tell it now. - -Ben. Be rul'd by me; forget to think of her. - -Rom. O! teach me how I should forget to think. - -Ben. By giving liberty unto thine eyes: - -Examine other beauties. - -Rom. 'Tis the way - -To call her's, exquisite, in question more. - -These happy masks, that kiss fair ladies' brows, - -Being black, put us in mind they hide the fair: - -He, that is stricken blind, cannot forget - -The precious treasure of his eyesight lost. - -Show me a mistress that is passing fair, - -What doth her beauty serve, but as a note - -Where I may read who pass'd that passing fair? - -Farewell: thou canst not teach me to forget. - -Ben. I'll pay that doctrine, or else die in debt. [Exeunt. - - - - - -An image should appear at this position in the text. - -If you are able to provide it, see Wikisource:Image guidelines and Help:Adding images for guidance. - - - -(Verona.) - - - - - -​ Scene II.—A Street. - -Enter Capulet, Paris, and Servant. - - - -Cap. But Montague is bound as well as I, - -In penalty alike; and 'tis not hard, I think, - -For men so old as we to keep the peace. - -Par. Of honourable reckoning are you both; - -And pity 'tis, you liv'd at odds so long. - -But now, my lord, what say you to my suit? - -Cap. But saying o'er what I have said before; - -My child is yet a stranger in the world, - -She hath not seen the change of fourteen years: - -Let two more summers wither in their pride, - -Ere we may think her ripe to be bride. - -Par. Younger than she are happy mothers made. - -Cap. And too soon marr'd are those so early made. - -Earth hath swallowed all my hopes but she, - -She is the hopeful lady of my earth: - -But woo her, gentle Paris, get her heart, - -My will to her consent is but a part; - -An she agree, within her scope of choice - -Lies my consent and fair according voice. - -This night I hold an old accustom'd feast, - -Whereto I have invited many a guest, - -Such as I love; and you among the store, - -One more most welcome, makes my number more. - -At my poor house look to behold this night - -Earth-treading stars, that make dark heaven light: - -Such comfort, as do lusty young men feel, - -When well-apparel'd April on the heel - -Of limping winter treads, even such delight - -Among fresh female buds shall you see this night - -Inherit at my house: hear all, all see, - -And like her most, whose merit most shall be: - -Which, on more view of many, mine being one, - -May stand in number, though in reckoning none- - -Come, go with me—Go, sirrah, trudge about - -Through fair Verona; find those persons out, - - - -Whose names are written there, and to give them say, [Giving a paper. - -My house and welcome on their pleasure stay. [Exeunt Capulet and Paris. - -Serv. Find them out, whose names are written here? It is written, that the shoemaker should meddle with his yard, and the tailor with his last, the fisher with his pencil, and the painter with his nets; but I am sent to find those persons, whose names are here writ, and can never find what names the writing person hath here writ. I must to the learned:—in good time. - - - - - -Enter Benvolio and Romeo. - - - -Ben. Tut, man! one fire burns out another's burning, - -⁠One pain lessen'd by another's anguish; - -Turn giddy, and be holp by backward turning; - -⁠One desperate grief cures with another's languish: - -Take thou some new infection to thy eye, - -And the rank poison of the old will die. - -Rom. Your plantain leaf is excellent for that. - -Ben. For what, I pray thee? - -Rom. For your broken shin. - -Ben. Why, Romeo, are thou mad? - -Rom. Not mad, but bound more than a madman is: - -Shut up in prison, kept without my food, - -Whipp'd and tormented, and—Good-den, good fellow. - -Serv. God gi' good den.—I pray, sir, can you read? - -Rom. Ay, mine own fortune in my misery. - -Serv. Perhaps you have learn'd it without book; but I pray, can you read anything you see? - -Rom. Ay, if I know the letters, and the language. - -Serv. Ye say honestly. Rest you merry. - -Rom. Stay, fellow; I can read. [Reads. - -"Signior Martino, and his wife, and daughters; County Anselme, and his beauteous sisters; the lady widow of Vitruvio; Signior Placentio, and his lovely nieces; Mercutio, and his brother Valentine; mine uncle Capulet, his wife, and daughters; my fair niece Rosaline; Livia; Signior Valentio, and his cousin Tybalt; Lucio, and the lively Helena." - -A fair assembly; whither should they come? - -Serv. Up. - -Rom. Whither? to supper? - -Serv. To our house. - -Rom. Whose house? - -Serv. My master's. - -Rom. Indeed, I should have asked you that before. - -Serv. Now, I'll tell you without asking. My master is the great rich Capulet; and if you be not of the house of Montagues, I pray, come and crush a cup of wine. Rest you merry. [Exit. - -Ben. At this same ancient feast of Capulet's - -Sups the fair Rosaline, whom thou so lov'st, - -With all the admired beauties of Verona: - -Go thither; and, with unattainted eye, - -Compare her face with some that I shall show, - -And I will make thee think thy swan a crow. - -Rom. When the devout religion of mine eye - -⁠Maintains such falsehood, then turns tears to fires; - -And these, who, often drown'd, could never die, - -⁠Transparent heretics, be burnt for liars. - -One fairer than my love! the all-seeing sun - -Ne'er saw her match, since first the world begun. - -Ben. Tut! you saw her fair, none else being by, - -Herself pois'd with herself in either eye; - -But in those crystal scales, let there be weigh'd - -Your lady's love against some other maid, - -That I will show you shining at this feast, - -And she shall scant show well, that now shows best. - -Rom. I'll go along, no such sight to be shown, - - - -But to rejoice in splendour of mine own. [Exeunt. - - - - - -Scene III—A Room in Capulet's House. - -Enter Lady Capulet and Nurse. - - - -La. Cap. Nurse, where's my daughter? call her forth to me. - -Nurse. Now, by my maiden-head at twelve year old, - -I bade her come—What, lamb! what, lady-bird!— - -God forbid!—where's this girl?—what, Juliet! - - - - - -Enter Juliet. - - - -Jul. How now! who calls? - -Nurse. Your mother. - -Jul. Madam, I am here. - -What is your will? - -La. Cap. This is the matter.—Nurse, give leave awhile, - -We must talk in secret.—Nurse, come back again: - -I have remember'd me, thou shalt hear our counsel. - -Thou know'st my daughter's of a pretty age. - -Nurse. 'Faith, I can tell her age unto an hour. - -La. Cap. She's not fourteen. - -Nurse. I'll lay fourteen on my teeth. - -And yet to my teen be it spoken I have but four, - -She is not fourteen. How long is it now - -To Lammas-tide? - - - -​La. Cap. A fortnight, and odd days. - -Nurse. Even or odd, of all days in the year, - -Come Lammas-eve at night shall she be fourteen. - -Susan and she,—God rest all Christian souls!— - -Were of an age.—Well, Susan is with God; - -She was too good for me. But, as I said, - -On Lammas-eve at night shall she be fourteen; - -That shall she, marry: I remember it well. - -'Tis since the earthquake now eleven years; - -And she was wean'd,—I never shall forget it,— - -Of all the days of the year, upon that day; - -For I had then laid wormwood to my dug, - -Sitting in the sun under the dove-house wall: - -My lord and you were then at Mantua.— - -Nay, I do bear a brain:—but, as I said, - -When it did taste the wormwood on the nipple - -Of my dug, and felt it bitter, pretty fool, - -To see it tetchy, and fall out with the dug! - -Shake, quoth the dove-house: 'twas no need, I trow, - -To bid me trudge. - -And since that time it is eleven years; - -For then she could stand alone; nay, by the rood, - -She could have run and waddled all about, - -For even the day before she broke her brow: - -And then my husband—God be with his soul! - -'A was a merry man,—took up the child: - -"Yea," quoth he, "dost thou fall upon thy face? - - - -Thou wilt fall backward, when thou hast more wit; - -An image should appear at this position in the text. - -If you are able to provide it, see Wikisource:Image guidelines and Help:Adding images for guidance. - - - - - -Wilt thou not, Jule?" and, by my holy-dam, - -The pretty wretch left crying, and said—"Ay." - -To see, now, how a jest shall come about! - -I warrant, an I should live a thousand years. - -I never should forget it: "Wilt thou not, Jule?" quoth he: - -And, pretty fool, it stinted, and said—"Ay." - -La. Cap. Enough of this: I pray thee: hold they peace. - -Nurse. Yes, madam. Yet I cannot choose but laugh, - -To think it should leave crying, and say—"Ay:" - -And yet, I warrant, it had upon its brow - -A bump as big as a young cockerel's stone, - -A perilous knock; and it cried bitterly. - -"Yea," quoth my husband, "fall'st upon thy face? - -Thou wilt fall backward, when thou com'st to age; - -Wilt thou not, Jule?" it stinted, and said—"Ay." - -Jul. And stint thou too, I pray thee, nurse, say I. - -Nurse. Peace, I have done. God mark thee to his grace! - -Thou was the prettiest babe that e'er I nurs'd: - -An I might live to see thee married once, - -I have my wish. - - - -​La. Cap. Marry, that marry is the very theme - -I came to talk of:—tell me, daughter Juliet, - -How stands your disposition to be married? - -Jul. It is an honour that I dream not of. - -Nurse. An honour! were not I thine only nurse, - -I would say, thou hadst sucked wisdom from thy teat. - -La. Cap. Well, think of marriage now; younger than you, - -Here in Verona, ladies of esteem, - -Are made already mothers: by my count, - -I was your mother, much upon these years - -That you are now a maid. Thus, then, in brief;— - -The valiant Paris seeks you for his love. - -Nurse. A man, young lady! lady, such a man, - -As all the world—Why, he's a man of wax. - -La. Cap. Verona's summer hath not such a flower. - -Nurse. Nay, he's a flower; in faith, a very flower. - -La. Cap. What say you? can you love the gentleman? - -This night you shall behold him at out' feast: - -Read o'er the volume of young Paris' face, - -And find delight writ there with beauty's pen. - -Examine every married lineament, - -And see how one another lends content; - -And what obscur'd in this fair volume lies, - -Find written in the margin of his eyes. - -This precious book of love, this unbound lover, - -To beautify him, only lacks a cover: - -The fish lives in the sea; and 'tis much pride, - -For fair without the fair within to hide. - -That book in many's eyes doth share the glory, - -That in gold clasps locks in the golden story; - -So shall you share all that he doth possess, - -By having him making yourself no less. - -Nurse. No less? nay, bigger: women grow by men. - -La. Cap. Speak briefly, can you like of Paris' love? - -Jul. I'll look to like, if looking liking move; - -But no more deep will I endart mine eye, - -Than your consent gives strength to make it fly. - -Enter a Servant. - - - -Serv. Madam, the guests are come, supper served up, you called, my young lady asked for, the nurse cursed in the pantry, and every thing in extremity. I must hence to wait; I beseech you, follow straight. - -La. Cap. We follow thee. Juliet, the county stays. - -Nurse. Go, girl, seek happy nights to happy days. [Exeunt. - - - - - -Scene IV.—A Street. - -Enter Romeo, Mercutio, Benvolio, with five or six Maskers, Torch-bearers, and others. - - - -Rom. What, shall this speech be spoke for our excuse, - -Or shall we on without apology? - -Ben. The date is out of such prolixity: - -We'll have no Cupid hood-wink'd with a scarf, - -Bearing a Tartar's painted bow of lath, - -Scaring the ladies like a crow-keeper; - -Nor no without-book prologue, faintly spoke - -After the prompter, for our entrance: - -But, let them measure us by what they will, - -We'll measure them a measure, and be gone. - -Rom. Give me a torch; I am not for this ambling: - -Being but heavy, I will bear the light. - -Mer. Nay, gentle Romeo, we must have you dance. - -Rom. Not I, believe me. You have dancing shoes, - -With nimble soles; I have a soul of lead, - -So stakes me to the ground, I cannot move. - -Mer. You are a lover: borrow Cupid's wings, - -And soar with them above a common bound. - -Rom. I am too sore enpierced with his shaft, - -To soar with his light feathers; and so bound, - -I cannot bound a pitch above dull woe: - -Under love's heavy burden do I sink. - -Mer. And, to sink in it, should you burden love; - -Too great oppression for a tender thing. - -Rom. Is love a tender thing? it is too rough, - -Too rude, too boisterous; and it pricks like thorn. - -Mer. If love be rough with you, be rough with love; - -Prick love for pricking, and you beat love down.— - - - -Give me a case to put my visage in: [Putting on a mask. - -A visor for a visor!—what care I, - -What curious eye doth quote deformities? - -Here are the beetle-brows shall blush for me. - -Ben. Come, knock, and enter; and no sooner in, - -But every man betake him to his legs. - -Rom. A torch for me: let wantons, light of heart, - -Tickle the senseless rushes with their heels; - -For I am proverb'd with a grandsire phrase,— - -I'll be a candle-holder, and look on: - -The game was ne'er so fair, and I am done. - -Mer. Tut! dun's the mouse, the constable's own word. - -If thou art dun, we'll draw thee from the mire - -Of this save-reverence love, wherein thou stick'st - -Up to the ears.—Come, we burn day-light, ho. - -Rom. Nay, that's not so. - -Mer. I mean, sir, in delay - -We waste our lights in vain, like lamps by day. - -Take our good meaning, for our judgment sits - -Five times in that, ere once in our five wits. - -Rom. And we mean well in going to this mask, - -But 'tis no wit to go. - -Mer. Why, may one ask? - -Rom. I dreamt a dream to-night? - -Mer. And so did I. - -Rom. Well, what was yours? - -Mer. That dreamers often lie. - -Rom. In bed asleep, while they do dream things true. - -Mer. O! then, I see, queen Mab hath been with you. - -She is the fairies' midwife; and she comes - -In shape no bigger than an agate-stone - -On the fore-finger of an alderman, - -Drawn with a team of little atomies - -Over men's noses as they lie asleep: - -Her waggon-spokes made of long spinners' legs; - -The cover, of the wings of grasshoppers; - -The traces, of the smallest spider's web; - -The collars, of the moonshine's watery beams: - -Her whip, of cricket's bone; the lash, of film: - -Her waggoner, a small grey-coated gnat, - -Not half so big as a round little worm - -Prick'd from the lazy finger of a maid. - -Her chariot is an empty hazel-nut, - -Made by the joiner squirrel, or old grub, - -Time out of mind the fairies' coach-makers. - -And in this state she gallops night by night - -Through lovers' brains, and then they dream of love: ​ - -On courtiers' knees, that dream on court'sies straight: - -O'er lawyers' fingers, who straight dream on fees: - -O'er ladies' lips, who straight on kisses dream; - -Which oft the angry Mab with blisters plagues, - -Because their breaths with sweet-meats tainted are. - -Sometime she gallops o'er a courtier's nose, - -And then dreams he of smelling out a suit: - -And sometime comes she with a tithe-pig's tail, - -Tickling a parson's nose as 'a lies asleep; - -Then he dreams of another benefice. - -Sometime she driveth o'er a soldier's neck, - -And then dreams he of cutting foreign throats, - -Of breaches, ambuscadoes, Spanish blades, - -Of healths five fathom deep; and then anon - -Drums in his ear, at which he starts, and wakes; - -And, being thus frighted, swears a prayer or two, - -And sleeps again. This is that very Mab, - -That plats the manes of horses in the night; - -And bakes the elf-locks in foul sluttish hairs, - -Which, once untangled, much misfortune bodes. - -This is the hag, when maids lie on their backs, - -That presses them, and learns them first to bear, - -Making them women of good carriage. - -This, is she— - -Rom. Peace, peace! Mercutio, peace! - -Thou talk'st of nothing. - -Mer. True, I talk of dreams, - -Which are the children of an idle brain, - -Begot of nothing but vain fantasy; - -Which is as thin of substance as the air; - -And more inconstant than the wind, who woos - -Even now the frozen bosom of the north, - -And, being anger'd, puffs away from thence, - -Turning his face to the dew-dropping south. - -Ben. This wind, you talk of, blows us from ourselves; - -Supper is done, and we shall come too late. - -Rom. I fear, too early; for my mind misgives, - -Some consequence, yet hanging in the stars, - -Shall bitterly begin his fearful date - -With this night's revels; and expire the term - -Of a despised life, clos'd in my breast, - -By some vile forfeit of untimely death: - -But He, that hath the steerage of my course, - -Direct my sail.—On, lusty gentlemen. - -Ben. Strike, drum. [Exeunt - - - - - -An image should appear at this position in the text. - -If you are able to provide it, see Wikisource:Image guidelines and Help:Adding images for guidance. - - - -('Court-cupboard,' and Plate.) - - - - - -Scene V.—A Hall in Capulet's House. - -Musicians waiting. Enter Servants. - - - -1 Serv. Where's Potpan, that he helps not to take away? he shift a trencher! he scrape a trencher! - -2 Serv. When good manners shall lie all in one or two men's hands, and they unwashed too, 'tis a foul thing. - -1 Serv. Away with the joint-stools, remove the court-cupboard, look to the plate.—Good thou, save ​me a piece of marchpane; and, as thou lovest me, let the porter let in Susan Grindstone, and Nell.—Antony! and Potpan! - -2 Serv. Ay, boy; ready. - -1 Serv. You are looked for, and called for, asked for, and sought for, in the great chamber. - -2 Serv. We cannot be here and there too.—Cheerly, boys: be brisk awhile, and the longer liver take all. [They retire behind. - -Enter Capulet, &c., with the Guests, and the Maskers. - - - -Cap. Welcome, gentlemen! Ladies that have their toes - -Unplagu'd with corns, will have a bout with you:— - -Ah ha, my mistresses! which of you all - -Will now deny to dance? she that makes dainty, she, - -I'll swear, hath corns. Am I come near you now? - -You are welcome, gentlemen! I have seen the day, - -That I have worn a visor, and could tell - -A whispering tale in a fair lady's ear, - -Such as would please:—'tis gone, 'tis gone, 'tis gone. - -You are welcome, gentlemen!—Come, musicians, play. - - - -A hall! a hall! give room, and foot it, girls. [Music plays, and they dance. - -More light, ye knaves! and turn the tables up, - -And quench the fire, the room is grown too hot.— - -Ah! sirrah, this unlook'd-for sport comes well. - -Nay, sit, nay, sit, good cousin Capulet, - -For you and I are past our dancing days: - -How long is't now, since last yourself and I - -Were in a mask? - -2 Cap. By'r lady, thirty years. - -1 Cap. What, man! 'tis not so much, 'tis not so much: - -'Tis since the nuptial of Lucentio, - -Come Pentecost as quickly as it will, - -Some five and twenty years; and then we mask'd. - -2 Cap. 'Tis more, 'tis more: his son is elder, sir; - -His son is thirty. - -1 Cap. Will you tell me that? - -His son was but a ward two years ago. - -Rom. What lady is that, which doth enrich the hand - -Of yonder knight? - -Serv. I know not, sir. - -Rom. O! she doth teach the torches to burn bright. - -Her beauty hangs upon the cheek of night - -Like a rich jewel in an Æthiop's ear; - -Beauty too rich for use, for earth too dear! - -So shows a snowy dove trooping with crows, - -As yonder lady o'er her fellows shows. - -The measure done, I'll watch her place of stand, - -And, touching hers, make blessed my rude hand. - -Did my heart love till now? forswear it, sight! - -I never saw true beauty till this night. - -Tyb. This, by his voice, should be a Montague.— - -Fetch me my rapier, boy.—What! dares the slave - -Come hither, cover'd with an antic face, - -To fleer and scorn at our solemnity? - -Now, by the stock and honour of my kin, - -To strike him dead I hold it not a sin. - -1 Cap. Why, how now, kinsman! wherefore storm you so? - -Tyb. Uncle, this is a Montague, our foe; - -A villain, that is hither come in spite, - -To scorn at our solemnity this night. - -1 Cap. Young Romeo is it? - -Tyb. 'Tis he, that villain Romeo. - -1 Cap. Content thee, gentle coz, let him alone, - -He bears him like a portly gentleman; - -And, to say truth, Verona brags of him, - -To be a virtuous and well-govern'd youth. - -I would not for the wealth of all this town, - -Here, in my house, do him disparagement; - -Therefore, be patient, take no note of him: - -It is my will; the which if thou respect, - -Show a fair presence, and put off these frowns, - -An ill-beseeming semblance for a feast. - -Tyb. It fits, when such a villain is a guest. - -I'll not endure him. - -1 Cap. He shall be endur'd: - -What! goodman boy!—I say, he shall;—go to;— - -Am I the master here, or you? go to. - -You'll not endure him! God shall mend my soul— - -You'll make a mutiny among my guests. - -You will set cock-a-hoop! you'll be the man! - -Tyb. Why, uncle, 'tis a shame. - -1 Cap. Go to, go to; - -You are a saucy boy.—Is't so, indeed?— - -This trick may chance to scath you;—I know what. - -You must contrary me! marry, 'tis time— - -Well said, my hearts!—You are a princox; go:— - -Be quiet, or—More light, more light!—for shame! - -I'll make you quiet;—What!—Cheerly, my hearts! - -Tyb. Patience perforce with wilful choler meeting, - -Makes my flesh tremble in their different greeting. - -I will withdraw: but this intrusion shall, - - - -Now seeming sweet, convert to bitter gall. [Exit. - -Rom. If I profane with my unworthiest hand [To Juliet. - -⁠This holy shrine, the gentle fine is this,— - -My lips, two blushing pilgrims, ready stand - -⁠To smooth that rough touch with a tender kiss. - -Jul. Good pilgrim, you do wrong your hand too much, - -⁠Which mannerly devotion shows in this; - -For saints have hands that pilgrims' hands do touch, - -⁠And palm to palm is holy palmers' kiss. - -Rom. Have not saints lips, and holy palmers too? - -Jul. Ay, pilgrim, lips that they must use in prayer. - -Rom. O! then, dear saint, let lips do what hands do; - -They pray, grant thou, lest faith turn to despair. - -Jul. Saints do not move, though grant for prayers' sake. - -Rom. Then move not, while my prayer's effect I take. - - - -Thus from my lips, by thine, my sin is purg'd. [Kissing her. - -Jul. Then have my lips the sin that they have took. - -Rom. Sin from my lips? O, trespass sweetly urg'd! - -Give me my sin again. - -Jul. You kiss by the book. - -Nurse. Madam, your mother craves a word with you. - -Rom. What is her mother? - -Nurse. Marry, bachelor, - -Her mother is the lady of the house, - -And a good lady, and a wise, and virtuous. - -I nurs'd her daughter, that you talk'd withal; - -I tell you—he that can lay hold of her - -Shall have the chinks. - - - -​Rom. Is she a Capulet? - -O, dear account! my life is my foe's debt. - -Ben. Away, begone: the sport is at the best. - -Rom. Ay, so I fear; the more is my unrest. - -1 Cap. Nay, gentlemen, prepare not to be gone; - -We have a trifling foolish banquet towards.— - -Is it e'en so? Why then, I thank you all; - -I thank you, honest gentlemen; good night:— - -More torches here!—Come on, then let's to bed. - -Ah, sirrah, by my fay, it waxes late; - - - -I'll to my rest. [Exeunt all but Juliet and Nurse. - -Jul. Come hither, nurse. What is yond' gentleman? - -Nurse. The son and heir of old Tiberio. - -Jul. What's he, that now is going out of door? - -Nurse. Marry, that, I think, be young Petruchio. - -Jul. What's he, that follows here, that would not dance? - -Nurse. I know not. - -Jul. Go, ask his name.—If he be married, - -My grave is like to be my wedding bed. - -Nurse. His name is Romeo, and a Montague; - -The only son of your great enemy. - -Jul. My only love sprung from my only hate! - -Too early seen unknown, and known too late! - -Prodigious birth of love it is to me, - -That I must love a loathed enemy. - -Nurse. What's this? what's this? - -Jul. A rhyme I learn'd even now - -Of one I danc'd withal. [One calls within, Juliet! - -Nurse. Anon, anon: - -Come, let's away; the strangers all are gone. [Exeunt. - - - - - -Enter Chorus. - - - -Now old desire doth in his death-bed lie, - -And young affection gapes to be his heir: - -That fair, for which love groan'd for, and would die, - -With tender Juliet match'd is now not fair. - -Now Romeo is belov'd, and loves again, - -Alike bewitched by the charm of looks; - -But to his foe suppos'd he must complain, - -And she steal love's sweet bait from fearful hooks: - -Being held a foe, he may not have access - -To breathe such vows as lovers use to swear; - -And she as much in love, her means much less - -To meet her new-beloved anywhere: - -But passion lends them power, time, means, to meet, - - - -Tempering extremities with extreme sweet. [Exit. - - - - - -An image should appear at this position in the text. - -If you are able to provide it, see Wikisource:Image guidelines and Help:Adding images for guidance. - - - - - -About this digital edition - - -This e-book comes from the online library Wikisource[1]. This multilingual digital library, built by volunteers, is committed to developing a free accessible collection of publications of every kind: novels, poems, magazines, letters... - -We distribute our books for free, starting from works not copyrighted or published under a free license. You are free to use our e-books for any purpose (including commercial exploitation), under the terms of the Creative Commons Attribution-ShareAlike 3.0 Unported[2] license or, at your choice, those of the GNU FDL[3]. - -Wikisource is constantly looking for new members. During the realization of this book, it's possible that we made some errors. You can report them at this page[4]. - -The following users contributed to this book: - -Angelprincess72 - -Djr13 - -ThomasBot - -BirgitteSB - -Mpaa - -Beleg Tâl - -Einstein95 - -Kathleen.wright5 - -EncycloPetey - -Dariyman - - - - - -* * * - - - -↑ http://wikisource.org - -↑ http://www.creativecommons.org/licenses/by-sa/3.0 - -↑ http://www.gnu.org/copyleft/fdl.html - -↑ http://wikisource.org/wiki/Wikisource:Scriptorium - - - - - diff --git a/tests/fixtures/case_meetup.ics b/tests/fixtures/case_meetup.ics deleted file mode 100644 index 67f42cf3..00000000 --- a/tests/fixtures/case_meetup.ics +++ /dev/null @@ -1,78 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//Meetup//RemoteApi//EN -CALSCALE:GREGORIAN -METHOD:PUBLISH -X-ORIGINAL-URL:http://www.meetup.com/DevOpsDC/events/ical/DevOpsDC/ -X-WR-CALNAME:Events - DevOpsDC -BEGIN:VTIMEZONE -TZID:America/New_York -TZURL:http://tzurl.org/zoneinfo-outlook/America/New_York -X-LIC-LOCATION:America/New_York -BEGIN:DAYLIGHT -TZOFFSETFROM:-0500 -TZOFFSETTO:-0400 -TZNAME:EDT -DTSTART:19700308T020000 -RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU -END:DAYLIGHT -BEGIN:STANDARD -TZOFFSETFROM:-0400 -TZOFFSETTO:-0500 -TZNAME:EST -DTSTART:19701101T020000 -RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU -END:STANDARD -END:VTIMEZONE -BEGIN:VEVENT -DTSTAMP:20120605T003759Z -DTSTART;TZID=America/New_York:20120712T183000 -DTEND;TZID=America/New_York:20120712T213000 -STATUS:CONFIRMED -SUMMARY:DevOps DC Meetup -DESCRIPTION:DevOpsDC\nThursday\, July 12 at 6:30 PM\n\nThis will be a joi - nt meetup / hack night with the DC jQuery Users Group. The idea behind - the hack night: Small teams consisting of at least 1 member...\n\nDeta - ils: http://www.meetup.com/DevOpsDC/events/47635522/ -CLASS:PUBLIC -CREATED:20120111T120339Z -GEO:38.90;-77.01 -LOCATION:Fathom Creative\, Inc. (1333 14th Street Northwest\, Washington - D.C.\, DC 20005) -URL:http://www.meetup.com/DevOpsDC/events/47635522/ -LAST-MODIFIED:20120522T174406Z -UID:event_qtkfrcyqkbnb@meetup.com -END:VEVENT -BEGIN:VEVENT -DTSTAMP:20120605T003759Z -DTSTART;TZID=America/New_York:20120911T183000 -DTEND;TZID=America/New_York:20120911T213000 -STATUS:CONFIRMED -SUMMARY:DevOps DC Meetup -DESCRIPTION:DevOpsDC\nTuesday\, September 11 at 6:30 PM\n\n \n\nDetails: - http://www.meetup.com/DevOpsDC/events/47635532/ -CLASS:PUBLIC -CREATED:20120111T120352Z -GEO:38.90;-77.01 -LOCATION:CustomInk\, LLC (7902 Westpark Drive\, McLean\, VA 22102) -URL:http://www.meetup.com/DevOpsDC/events/47635532/ -LAST-MODIFIED:20120316T202210Z -UID:event_qtkfrcyqmbpb@meetup.com -END:VEVENT -BEGIN:VEVENT -DTSTAMP:20120605T003759Z -DTSTART;TZID=America/New_York:20121113T183000 -DTEND;TZID=America/New_York:20121113T213000 -STATUS:CONFIRMED -SUMMARY:DevOps DC Meetup -DESCRIPTION:DevOpsDC\nTuesday\, November 13 at 6:30 PM\n\n \n\nDetails: h - ttp://www.meetup.com/DevOpsDC/events/47635552/ -CLASS:PUBLIC -CREATED:20120111T120402Z -GEO:38.90;-77.01 -LOCATION:CustomInk\, LLC (7902 Westpark Drive\, McLean\, VA 22102) -URL:http://www.meetup.com/DevOpsDC/events/47635552/ -LAST-MODIFIED:20120316T202210Z -UID:event_qtkfrcyqpbrb@meetup.com -END:VEVENT -END:VCALENDAR diff --git a/tests/fixtures/encoding.ics b/tests/fixtures/encoding.ics deleted file mode 100644 index 5a0047eb..00000000 --- a/tests/fixtures/encoding.ics +++ /dev/null @@ -1,16 +0,0 @@ -BEGIN:VCALENDAR -PRODID:-//Plönë.org//NONSGML plone.app.event//EN -VERSION:2.0 -X-WR-CALNAME:äöü ÄÖÜ € -X-WR-CALDESC:test non ascii: äöü ÄÖÜ € -X-WR-RELCALID:12345 -BEGIN:VEVENT -DTSTART:20101010T100000Z -DTEND:20101010T120000Z -CREATED:20101010T100000Z -UID:123456 -SUMMARY:Non-ASCII Test: ÄÖÜ äöü € -DESCRIPTION:icalendar should be able to handle non-ascii: €äüöÄÜÖ. -LOCATION:Tribstrül -END:VEVENT -END:VCALENDAR diff --git a/tests/fixtures/groupscheduled.ics b/tests/fixtures/groupscheduled.ics deleted file mode 100644 index 7fc4b6f6..00000000 --- a/tests/fixtures/groupscheduled.ics +++ /dev/null @@ -1,36 +0,0 @@ -BEGIN:VCALENDAR -PRODID:-//RDU Software//NONSGML HandCal//EN -VERSION:2.0 -BEGIN:VTIMEZONE -TZID:US-Eastern -BEGIN:STANDARD -DTSTART:19981025T020000 -RDATE:19981025T020000 -TZOFFSETFROM:-0400 -TZOFFSETTO:-0500 -TZNAME:EST -END:STANDARD -BEGIN:DAYLIGHT -DTSTART:19990404T020000 -RDATE:19990404T020000 -TZOFFSETFROM:-0500 -TZOFFSETTO:-0400 -TZNAME:EDT -END:DAYLIGHT -END:VTIMEZONE -BEGIN:VEVENT -DTSTAMP:19980309T231000Z -UID:guid-1.host1.com -ORGANIZER;ROLE=CHAIR:MAILTO:mrbig@host.com -ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT;CUTYPE=GROUP: -MAILTO:employee-A@host.com -DESCRIPTION:Project XYZ Review Meeting -CATEGORIES:MEETING -CLASS:PUBLIC -CREATED:19980309T130000Z -SUMMARY:XYZ Project Review -DTSTART;TZID=US-Eastern:19980312T083000 -DTEND;TZID=US-Eastern:19980312T093000 -LOCATION:1CP Conference Room 4350 -END:VEVENT -END:VCALENDAR diff --git a/tests/fixtures/multiple.ics b/tests/fixtures/multiple.ics deleted file mode 100644 index dbbde27b..00000000 --- a/tests/fixtures/multiple.ics +++ /dev/null @@ -1,80 +0,0 @@ -BEGIN:VCALENDAR -VERSION - - :2.0 -PRODID - - :-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN -METHOD - - :PUBLISH -BEGIN:VEVENT -UID - - :956630271 -SUMMARY - - :Christmas Day -CLASS - - :PUBLIC -X-MOZILLA-ALARM-DEFAULT-UNITS - - :minutes -X-MOZILLA-ALARM-DEFAULT-LENGTH - - :15 -X-MOZILLA-RECUR-DEFAULT-UNITS - - :weeks -X-MOZILLA-RECUR-DEFAULT-INTERVAL - - :1 -DTSTART - - ;VALUE=DATE - :20031225 -DTEND - - ;VALUE=DATE - :20031226 -DTSTAMP - - :20020430T114937Z -END:VEVENT -END:VCALENDAR -BEGIN:VCALENDAR -VERSION - :2.0 -PRODID - :-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN -METHOD - :PUBLISH -BEGIN:VEVENT -UID - :911737808 -SUMMARY - :Boxing Day -CLASS - :PUBLIC -X-MOZILLA-ALARM-DEFAULT-UNITS - :minutes -X-MOZILLA-ALARM-DEFAULT-LENGTH - :15 -X-MOZILLA-RECUR-DEFAULT-UNITS - :weeks -X-MOZILLA-RECUR-DEFAULT-INTERVAL - :1 -DTSTART - ;VALUE=DATE - :20030501 -DTSTAMP - :20020430T114937Z -END:VEVENT -BEGIN:VEVENT -UID - :wh4t3v3r -DTSTART;VALUE=DATE:20031225 -SUMMARY:Christmas again! -END:VEVENT -END:VCALENDAR diff --git a/tests/fixtures/recurrence.ics b/tests/fixtures/recurrence.ics deleted file mode 100644 index 6971bb27..00000000 --- a/tests/fixtures/recurrence.ics +++ /dev/null @@ -1,12 +0,0 @@ -BEGIN:VCALENDAR -METHOD:Request -PRODID:-//My product//mxm.dk/ -VERSION:2.0 -BEGIN:VEVENT -DTSTART:19960401T010000 -DTEND:19960401T020000 -RRULE:FREQ=DAILY;COUNT=100 -EXDATE:19960402T010000Z,19960403T010000Z,19960404T010000Z -SUMMARY:A recurring event with exdates -END:VEVENT -END:VCALENDAR diff --git a/tests/fixtures/small.ics b/tests/fixtures/small.ics deleted file mode 100644 index 8b0a193d..00000000 --- a/tests/fixtures/small.ics +++ /dev/null @@ -1,25 +0,0 @@ -BEGIN:VCALENDAR -METHOD:Request -PRODID:-//My product//mxm.dk/ -VERSION:2.0 -BEGIN:VEVENT -DESCRIPTION:This is a very long description that will be folded This is a - very long description that will be folded This is a very long description - that will be folded This is a very long description that will be folded Th - is is a very long description that will be folded This is a very long desc - ription that will be folded This is a very long description that will be f - olded This is a very long description that will be folded This is a very l - ong description that will be folded This is a very long description that w - ill be folded -PARTICIPANT;CN=Max M:MAILTO:maxm@mxm.dk -DTEND:20050107T160000 -DTSTART:20050107T120000 -SUMMARY:A second event -END:VEVENT -BEGIN:VEVENT -DTEND:20050108T235900 -DTSTART:20050108T230000 -SUMMARY:A single event -UID:42 -END:VEVENT -END:VCALENDAR diff --git a/tests/fixtures/spaces.ics b/tests/fixtures/spaces.ics deleted file mode 100644 index 39b1fc70..00000000 --- a/tests/fixtures/spaces.ics +++ /dev/null @@ -1,39 +0,0 @@ -BEGIN:VCALENDAR -PRODID:-//Nextcloud calendar v1.7.2 -VERSION:2.0 -CALSCALE:GREGORIAN -BEGIN:VEVENT -CREATED:20200223T183124 -DTSTAMP:20200223T183124 -LAST-MODIFIED:20200223T183124 -UID:JCGUPMSIMHOT80Q0CN3NV -SUMMARY:Spaces -CLASS:PUBLIC -DESCRIPTION:starting with 78 spaces\n - starting with 79 spaces\n\n\n\n - \n\n\n an - d\n also\n a\n lot\n of\n tabs\n even \n tra - iling \n ones -STATUS:CONFIRMED -DTSTART;TZID=Europe/Berlin:20200221T180000 -DTEND;TZID=Europe/Berlin:20200221T190000 -END:VEVENT -BEGIN:VTIMEZONE -TZID:Europe/Berlin -X-LIC-LOCATION:Europe/Berlin -BEGIN:DAYLIGHT -TZOFFSETFROM:+0100 -TZOFFSETTO:+0200 -TZNAME:CEST -DTSTART:19700329T020000 -RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU -END:DAYLIGHT -BEGIN:STANDARD -TZOFFSETFROM:+0200 -TZOFFSETTO:+0100 -TZNAME:CET -DTSTART:19701025T030000 -RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU -END:STANDARD -END:VTIMEZONE -END:VCALENDAR \ No newline at end of file diff --git a/tests/fixtures/time.ics b/tests/fixtures/time.ics deleted file mode 100644 index d730a4c4..00000000 --- a/tests/fixtures/time.ics +++ /dev/null @@ -1,3 +0,0 @@ -BEGIN:VCALENDAR -X-SOMETIME;VALUE=TIME:172010 -END:VCALENDAR diff --git a/tests/fixtures/timezoned.ics b/tests/fixtures/timezoned.ics deleted file mode 100644 index 5878b723..00000000 --- a/tests/fixtures/timezoned.ics +++ /dev/null @@ -1,36 +0,0 @@ -BEGIN:VCALENDAR -PRODID:-//Plone.org//NONSGML plone.app.event//EN -VERSION:2.0 -X-WR-CALNAME:test create calendar -X-WR-CALDESC:icalendar test -X-WR-RELCALID:12345 -X-WR-TIMEZONE:Europe/Vienna -BEGIN:VTIMEZONE -TZID:Europe/Vienna -X-LIC-LOCATION:Europe/Vienna -BEGIN:DAYLIGHT -TZOFFSETFROM:+0100 -TZOFFSETTO:+0200 -TZNAME:CEST -DTSTART:19700329T020000 -RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU -END:DAYLIGHT -BEGIN:STANDARD -TZOFFSETFROM:+0200 -TZOFFSETTO:+0100 -TZNAME:CET -DTSTART:19701025T030000 -RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU -END:STANDARD -END:VTIMEZONE -BEGIN:VEVENT -DTSTART;TZID=Europe/Vienna:20120213T100000 -DTEND;TZID=Europe/Vienna:20120217T180000 -DTSTAMP:20101010T091010Z -CREATED:20101010T091010Z -UID:123456 -SUMMARY:artsprint 2012 -DESCRIPTION:sprinting at the artsprint -LOCATION:aka bild, wien -END:VEVENT -END:VCALENDAR diff --git a/tests/fixtures/utf-8-emoji.ics b/tests/fixtures/utf-8-emoji.ics deleted file mode 100644 index 06fd8ba8..00000000 --- a/tests/fixtures/utf-8-emoji.ics +++ /dev/null @@ -1,6823 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -CALSCALE:GREGORIAN -PRODID:-//SabreDAV//SabreDAV//EN -X-WR-CALNAME:⟦UTF-8⟧ Test 🎉 -X-APPLE-CALENDAR-COLOR:#317CCC -BEGIN:VTIMEZONE -TZID:Europe/Berlin -X-LIC-LOCATION:Europe/Berlin -BEGIN:DAYLIGHT -TZOFFSETFROM:+0100 -TZOFFSETTO:+0200 -TZNAME:CEST -DTSTART:19700329T020000 -RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU -END:DAYLIGHT -BEGIN:STANDARD -TZOFFSETFROM:+0200 -TZOFFSETTO:+0100 -TZNAME:CET -DTSTART:19701025T030000 -RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU -END:STANDARD -END:VTIMEZONE -BEGIN:VEVENT -CREATED:20200221T214401 -DTSTAMP:20200221T214401 -LAST-MODIFIED:20200221T214401 -UID:WYVTMOHYEWH3NK8SACQP -SUMMARY:Emoji Default Style Values\, v13.0 © Unicode®\, Inc. -CLASS:PUBLIC -DESCRIPTION:Emoji Default Style Values\, v13.0\nThis text file provides a l - isting of characters for testing the display of emoji characters.\n• “ - -vs” indicates that emoji presentation selectors are not present\, and a - character has Emoji_Presentation=False.\n• “+es” indicates that emo - ji presentation selectors are present\, and a character has Emoji_Presenta - tion=False.\n• “+ts” indicates that text presentation selectors are - present\, and a character has Emoji_Presentation=False.\nFor more informat - ion on presentation style\, see UTS #51.\n\nShould all be colorful & monos - pace\, except that those marked with “text+ts” should be monochrome\, - and those with “text-vs” may vary by platform.\n\n# © 2019 Unicode®\ - , Inc.\n# Unicode and the Unicode Logo are registered trademarks of Unicod - e\, Inc. in the U.S. and other countries.\n# For terms of use\, see http:/ - /www.unicode.org/terms_of_use.html\n\ntext+ts\n〰︎ ‼︎ ⁉︎ *︎ - ⃣ #︎⃣ 〽︎ ©︎ ®︎ ↔︎ ↕︎ ↖︎ ↗︎ ↘︎ ↙︎ - ↩︎ ↪︎ ⌨︎ ⏏︎ ⏭︎ ⏮︎ ⏯︎\n⏱︎ ⏲︎ ⏸︎ ⏹ - ︎ ⏺︎ ▪︎ ▫︎ ▶︎ ◀︎ ◻︎ ◼︎ ☀︎ ☁︎ ☂︎ - ☃︎ ☄︎ ☎︎ ☑︎ ☘︎ ☝︎ ☠︎\n☢︎ ☣︎ ☦︎ ☪ - ︎ ☮︎ ☯︎ ☸︎ ☹︎ ☺︎ ♀︎ ♂︎ ♟︎ ♠︎ ♣︎ - ♥︎ ♦︎ ♨︎ ♻︎ ♾︎ ⚒︎ ⚔︎\n⚕︎ ⚖︎ ⚗︎ ⚙ - ︎ ⚛︎ ⚜︎ ⚠︎ ⚧︎ ⚰︎ ⚱︎ ⛈︎ ⛏︎ ⛑︎ ⛓︎ - ⛩︎ ⛰︎ ⛱︎ ⛴︎ ⛷︎ ⛸︎ ⛹︎\n✂︎ ✈︎ ✉︎ ✌ - ︎ ✍︎ ✏︎ ✒︎ ✔︎ ✖︎ ✝︎ ✡︎ ✳︎ ✴︎ ❄︎ - ❇︎ ❣︎ ❤︎ ➡︎ ⤴︎ ⤵︎ ⬅︎\n⬆︎ ⬇︎ 🌡︎ - 🌤︎ 🌥︎ 🌦︎ 🌧︎ 🌨︎ 🌩︎ 🌪︎ 🌫︎ 🌬︎ - 🌶︎ 🍽︎ 🎖︎ 🎗︎ 🎙︎ 🎚︎ 🎛︎ 🎞︎ 🎟︎\n - 🏋︎ 🏌︎ 🏍︎ 🏎︎ 🏔︎ 🏕︎ 🏖︎ 🏗︎ 🏘︎ - ��︎ 🏚︎ 🏛︎ 🏜︎ 🏝︎ 🏞︎ 🏟︎ 🏳︎ 🏵︎ - 🏷︎ 🐿︎ 👁︎\n📽︎ 🕉︎ 🕊︎ 🕯︎ 🕰︎ 🕳︎ - 🕴︎ 🕵︎ 🕶︎ 🕷︎ 🕸︎ 🕹︎ 🖇︎ 🖊︎ 🖋︎ - 🖌︎ 🖍︎ 🖐︎ 🖥︎ 🖨︎ 🖱︎\n🖲︎ 🖼︎ 🗂︎ - 🗃︎ ��︎ 🗑︎ 🗒︎ 🗓︎ 🗜︎ 🗝︎ 🗞︎ 🗡︎ - 🗣︎ 🗨︎ 🗯︎ 🗳︎ 🗺︎ 🛋︎ 🛍︎ 🛎︎ 🛏︎\n - 🛠︎ 🛡︎ 🛢︎ 🛣︎ 🛤︎ 🛥︎ 🛩︎ 🛰︎ 🛳︎ 0 - ︎⃣ 1︎⃣ 2︎⃣ 3︎⃣ 4︎⃣ 5︎⃣ 6︎⃣ 7︎⃣ 8︎⃣ 9 - ︎⃣ 🅰︎ 🅱︎\nℹ︎ Ⓜ︎ 🅾︎ 🅿︎ ™︎ 🈂︎ 🈷 - ︎ ㊗︎ ㊙︎\n\ntext-vs\n☺ ☹ ☠ ❣ ❤ 🕳 🗨 🗯 🖐 ✌ - ☝ ✍ �� 🕵 🕴 ⛷ 🏌 ⛹ 🏋 🗣 🐿\n🕊 🕷 🕸 🏵 - ☘ 🌶 🍽 🗺 🏔 ⛰ 🏕 🏖 🏜 🏝 🏞 🏟 🏛 �� 🏘 - 🏚 ⛩\n🏙 ♨ 🏎 🏍 🛣 🛤 🛢 🛳 ⛴ 🛥 ✈ 🛩 🛰 - 🛎 ⏱ ⏲ 🕰 🌡 ☀ ☁ ⛈\n🌤 🌥 🌦 🌧 🌨 🌩 🌪 - 🌫 🌬 ☂ ⛱ ❄ ☃ ☄ 🎗 🎟 🎖 ⛸ 🕹 ♠ ♥\n♦ ♣ ♟ - 🖼 🕶 🛍 ⛑ 🎙 🎚 🎛 ☎ 🖥 🖨 ⌨ 🖱 🖲 🎞 📽 - 🕯 🗞 🏷\n✉ 🗳 ✏ ✒ 🖋 🖊 🖌 🖍 🗂 🗒 🗓 🖇 - ✂ 🗃 🗄 🗑 🗝 ⛏ ⚒ 🛠 🗡\n⚔ 🛡 ⚙ 🗜 ⚖ ⛓ ⚗ - 🛏 🛋 ⚰ ⚱ ⚠ ☢ ☣ ⬆ ↗ ➡ ↘ ⬇ ↙ ⬅\n↖ ↕ ↔ ↩ - ↪ ⤴ ⤵ ⚛ 🕉 ✡ ☸ ☯ ✝ ☦ ☪ ☮ ▶ ⏭ ⏯ ◀ ⏮\n⏸ - ⏹ ⏺ ⏏ ♀ ♂ ⚧ ✖ ♾ ‼ ⁉ 〰 ⚕ ♻ ⚜ ☑ ✔ 〽 ✳ - ✴ ❇\n© ® ™ #⃣ *⃣ 0⃣ 1⃣ 2⃣ 3⃣ 4⃣ 5⃣ 6⃣ 7⃣ 8⃣ - 9⃣ 🅰 🅱 ℹ Ⓜ 🅾 🅿\n🈂 🈷 ㊗ ㊙ ◼ ◻ ▪ ▫ 🏳\n - \ntext+es\n☺️ ☹️ ☠️ ❣️ ❤️ 🕳️ 🗨️ 🗯️ 🖐 - ️ ✌️ ☝️ ✍️ 👁️ 🕵️ 🕴️ ⛷️ 🏌️ ⛹️ - 🏋️ 🗣️ 🐿️\n🕊️ 🕷️ 🕸️ 🏵️ ☘️ 🌶️ - 🍽️ 🗺️ 🏔️ ⛰️ 🏕️ 🏖️ 🏜️ 🏝️ 🏞️ - 🏟️ 🏛️ 🏗️ 🏘️ 🏚️ ⛩️\n🏙️ ♨️ 🏎️ - 🏍️ 🛣️ 🛤️ 🛢️ 🛳️ ⛴️ 🛥️ ✈️ 🛩️ 🛰 - ️ 🛎️ ⏱️ ⏲️ 🕰️ 🌡️ ☀️ ☁️ ⛈️\n🌤️ - 🌥️ 🌦️ 🌧️ 🌨️ 🌩️ 🌪️ 🌫️ 🌬️ ☂️ ⛱ - ️ ❄️ ☃️ ☄️ 🎗️ 🎟️ 🎖️ ⛸️ 🕹️ ♠️ ♥ - ️\n♦️ ♣️ ♟️ 🖼️ 🕶️ 🛍️ ⛑️ 🎙️ 🎚️ - 🎛️ ☎️ 🖥️ 🖨️ ⌨️ 🖱️ 🖲️ 🎞️ 📽️ 🕯 - ️ 🗞️ 🏷️\n✉️ 🗳️ ✏️ ✒️ 🖋️ 🖊️ 🖌️ - 🖍️ 🗂️ 🗒️ 🗓️ 🖇️ ✂️ 🗃️ 🗄️ 🗑️ - 🗝️ ⛏️ ⚒️ 🛠️ 🗡️\n⚔️ 🛡️ ⚙️ 🗜️ ⚖ - ️ ⛓️ ⚗️ 🛏️ 🛋️ ⚰️ ⚱️ ⚠️ ☢️ ☣️ ⬆ - ️ ↗️ ➡️ ↘️ ⬇️ ↙️ ⬅️\n↖️ ↕️ ↔️ ↩️ - ↪️ ⤴️ ⤵️ ⚛️ 🕉️ ✡️ ☸️ ☯️ ✝️ ☦️ - ☪️ ☮️ ▶️ ⏭️ ⏯️ ◀️ ⏮️\n⏸️ ⏹️ ⏺️ ⏏ - ️ ♀️ ♂️ ⚧️ ✖️ ♾️ ‼️ ⁉️ 〰️ ⚕️ ♻️ - ⚜️ ☑️ ✔️ 〽️ ✳️ ✴️ ❇️\n©️ ®️ ™️ #️ - ⃣ *️⃣ 0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️ - ⃣ 8️⃣ 9️⃣ 🅰️ 🅱️ ℹ️ Ⓜ️ 🅾️ 🅿️\n🈂️ - 🈷️ ㊗️ ㊙️ ◼️ ◻️ ▪️ ▫️ 🏳️\n\nemoji cps\n - 😀 😃 😄 😁 😆 😅 🤣 😂 🙂 🙃 😉 😊 😇 🥰 😍 - 🤩 😘 😗 😚 😙 🥲\n😋 😛 😜 🤪 😝 🤑 🤗 🤭 - 🤫 🤔 �� 🤨 😐 😑 😶 😏 😒 🙄 😬 🤥 😌\n😔 - 😪 🤤 😴 😷 🤒 🤕 🤢 🤮 🤧 🥵 🥶 🥴 😵 🤯 🤠 - 🥳 🥸 😎 🤓 🧐\n😕 😟 🙁 😮 😯 😲 😳 🥺 😦 - 😧 😨 😰 😥 😢 😭 😱 😖 😣 😞 😓 😩\n😫 🥱 - 😤 😡 😠 🤬 😈 👿 💀 💩 🤡 👹 👺 👻 👽 👾 🤖 - 😺 😸 😹 😻\n😼 😽 🙀 😿 😾 🙈 🙉 🙊 💋 💌 - 💘 💝 💖 💗 💓 💞 💕 💟 💔 🧡 💛\n💚 💙 💜 - 🤎 🖤 🤍 💯 💢 💥 💫 💦 💨 💣 💬 💭 💤 👋 🤚 - ✋ 🖖 👌\n🤌 🤏 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 - 👍 👎 ✊ 👊 🤛 🤜 👏 🙌 👐 🤲\n🤝 🙏 💅 🤳 💪 - 🦾 🦿 🦵 🦶 👂 🦻 👃 🧠 🫀 🫁 🦷 🦴 👀 👅 - 👄 👶\n🧒 👦 👧 🧑 👱 👨 🧔 👩 🧓 👴 👵 🙍 - 🙎 🙅 🙆 �� 🙋 🧏 🙇 🤦 🤷\n👮 💂 🥷 👷 🤴 - 👸 👳 👲 🧕 🤵 👰 🤰 🤱 👼 🎅 🤶 🦸 🦹 🧙 🧚 - 🧛\n🧜 🧝 🧞 🧟 💆 💇 🚶 🧍 🧎 🏃 💃 🕺 👯 - 🧖 🧗 🤺 🏇 🏂 🏄 🚣 🏊\n🚴 �� 🤸 🤼 🤽 🤾 - 🤹 🧘 🛀 🛌 👭 👫 👬 💏 💑 👪 👤 👥 🫂 👣 🦰 - \n🦱 🦳 🦲 🐵 🐒 🦍 🦧 🐶 🐕 🦮 🐩 🐺 🦊 🦝 - 🐱 🐈 🦁 🐯 🐅 🐆 🐴\n🐎 🦄 🦓 🦌 🦬 🐮 🐂 - 🐃 🐄 🐷 🐖 🐗 🐽 🐏 🐑 🐐 🐪 🐫 🦙 🦒 🐘\n - 🦣 🦏 🦛 🐭 🐁 🐀 🐹 🐰 🐇 🦫 🦔 🦇 �� 🐨 - 🐼 🦥 🦦 🦨 🦘 🦡 🐾\n🦃 🐔 🐓 🐣 🐤 🐥 🐦 - 🐧 🦅 🦆 🦢 🦉 🦤 🪶 🦩 🦚 🦜 🐸 🐊 🐢 🦎\n - 🐍 🐲 🐉 🦕 🦖 🐳 🐋 🐬 🦭 🐟 🐠 🐡 🦈 🐙 🐚 - 🐌 🦋 🐛 🐜 🐝 🪲\n🐞 🦗 🪳 🦂 🦟 🪰 🪱 🦠 - 💐 🌸 💮 🌹 🥀 🌺 🌻 🌼 🌷 🌱 🪴 🌲 🌳\n🌴 - 🌵 🌾 🌿 🍀 🍁 🍂 🍃 🍇 🍈 🍉 🍊 🍋 🍌 🍍 🥭 - 🍎 🍏 🍐 🍑 🍒\n🍓 🫐 🥝 🍅 🫒 🥥 🥑 🍆 🥔 - 🥕 🌽 🫑 🥒 🥬 🥦 🧄 🧅 🍄 🥜 🌰 🍞\n🥐 🥖 - 🫓 🥨 🥯 🥞 🧇 🧀 🍖 �� 🥩 🥓 🍔 🍟 🍕 🌭 - 🥪 🌮 🌯 🫔 🥙\n🧆 🥚 🍳 🥘 🍲 🫕 🥣 🥗 🍿 - 🧈 🧂 🥫 🍱 🍘 🍙 🍚 🍛 🍜 🍝 🍠 🍢\n🍣 🍤 - 🍥 🥮 🍡 🥟 🥠 🥡 🦀 🦞 🦐 🦑 🦪 🍦 🍧 🍨 🍩 - 🍪 🎂 🍰 🧁\n🥧 🍫 🍬 🍭 🍮 🍯 🍼 🥛 ☕ 🫖 - 🍵 🍶 🍾 🍷 🍸 🍹 🍺 🍻 🥂 🥃 🥤\n🧋 🧃 🧉 - 🧊 🥢 🍴 🥄 🔪 🏺 🌍 🌎 🌏 🌐 🗾 🧭 🌋 🗻 🧱 - 🪨 🪵 🛖\n🏠 🏡 🏢 🏣 🏤 🏥 🏦 🏨 🏩 🏪 🏫 - 🏬 🏭 🏯 🏰 💒 🗼 🗽 ⛪ 🕌 🛕\n🕍 🕋 ⛲ ⛺ 🌁 - 🌃 🌄 🌅 🌆 🌇 🌉 🎠 🎡 🎢 💈 🎪 🚂 🚃 🚄 🚅 - 🚆\n🚇 🚈 🚉 🚊 🚝 🚞 🚋 🚌 🚍 🚎 🚐 �� 🚒 - 🚓 🚔 🚕 🚖 🚗 🚘 🚙 🛻\n🚚 🚛 🚜 🛵 🦽 🦼 - 🛺 🚲 🛴 🛹 🛼 🚏 ⛽ 🚨 🚥 �� 🛑 🚧 ⚓ ⛵ 🛶\ - n🚤 🚢 🛫 🛬 🪂 💺 🚁 🚟 🚠 🚡 🚀 🛸 🧳 ⌛ ⏳ - ⌚ ⏰ 🕛 🕧 🕐 🕜\n🕑 🕝 🕒 🕞 🕓 🕟 🕔 🕠 🕕 - 🕡 🕖 🕢 🕗 🕣 🕘 🕤 🕙 🕥 🕚 🕦 🌑\n🌒 🌓 - 🌔 �� 🌖 🌗 🌘 🌙 🌚 🌛 🌜 🌝 🌞 🪐 ⭐ 🌟 - 🌠 🌌 ⛅ 🌀 🌈\n🌂 ☔ ⚡ ⛄ 🔥 💧 🌊 🎃 🎄 🎆 - 🎇 🧨 ✨ 🎈 🎉 🎊 🎋 🎍 🎎 🎏 🎐\n🎑 🧧 🎀 🎁 - 🎫 🏆 🏅 🥇 🥈 🥉 ⚽ ⚾ 🥎 �� 🏐 🏈 🏉 🎾 - 🥏 🎳 🏏\n🏑 🏒 🥍 🏓 🏸 🥊 🥋 🥅 ⛳ 🎣 🤿 🎽 - 🎿 🛷 🥌 🎯 🪀 �� 🎱 🔮 🪄\n🧿 🎮 🎰 🎲 🧩 - 🧸 🪅 🪆 🃏 🀄 🎴 🎭 🎨 🧵 🪡 🧶 🪢 👓 🥽 🥼 - 🦺\n👔 👕 👖 🧣 🧤 🧥 🧦 👗 👘 🥻 🩱 🩲 🩳 - 👙 👚 👛 👜 👝 🎒 🩴 👞\n👟 🥾 🥿 �� 👡 🩰 - 👢 👑 👒 🎩 🎓 🧢 🪖 📿 💄 💍 💎 🔇 🔈 🔉 🔊 - \n📢 📣 📯 🔔 🔕 🎼 🎵 🎶 🎤 🎧 📻 🎷 🪗 🎸 - 🎹 🎺 🎻 🪕 🥁 🪘 📱\n📲 📞 📟 📠 🔋 🔌 💻 - 💽 💾 💿 📀 🧮 🎥 🎬 📺 📷 📸 📹 📼 🔍 🔎\n - 💡 🔦 🏮 🪔 📔 📕 📖 📗 📘 📙 📚 📓 📒 📃 � - � 📄 📰 📑 🔖 💰 🪙\n💴 💵 💶 💷 💸 💳 🧾 💹 - 📧 📨 📩 📤 📥 📦 📫 📪 📬 📭 📮 📝 💼\n📁 - 📂 📅 📆 📇 📈 📉 📊 📋 📌 📍 📎 📏 📐 🔒 🔓 - 🔏 🔐 🔑 🔨 🪓\n�� 🪃 🏹 🪚 🔧 🪛 🔩 🦯 🔗 - 🪝 🧰 🧲 🪜 🧪 🧫 🧬 🔬 🔭 📡 💉 🩸\n💊 🩹 - 🩺 🚪 🛗 🪞 🪟 🪑 🚽 🪠 🚿 🛁 🪤 🪒 🧴 🧷 🧹 - 🧺 🧻 🪣 🧼\n🪥 🧽 🧯 🛒 🚬 🪦 🗿 🪧 🏧 🚮 - 🚰 ♿ 🚹 🚺 🚻 🚼 🚾 🛂 🛃 🛄 🛅\n🚸 ⛔ 🚫 🚳 - 🚭 🚯 🚱 🚷 📵 🔞 🔃 🔄 🔙 🔚 🔛 🔜 🔝 🛐 🕎 - 🔯 ♈\n♉ ♊ ♋ ♌ ♍ ♎ ♏ ♐ ♑ ♒ ♓ ⛎ 🔀 🔁 🔂 - ⏩ ⏪ 🔼 ⏫ 🔽 ⏬\n🎦 🔅 🔆 📶 📳 📴 ➕ ➖ ➗ ❓ - ❔ ❕ ❗ 💱 💲 🔱 📛 🔰 ⭕ ✅ ❌\n❎ ➰ ➿ 🔟 🔠 - 🔡 🔢 🔣 🔤 🆎 🆑 🆒 🆓 🆔 🆕 🆖 🆗 🆘 🆙 🆚 - 🈁\n🈶 🈯 🉐 🈹 🈚 🈲 🉑 🈸 🈴 🈳 🈺 🈵 🔴 - 🟠 🟡 🟢 🔵 🟣 🟤 ⚫ ⚪\n🟥 🟧 🟨 🟩 🟦 🟪 🟫 - ⬛ ⬜ ◾ ◽ 🔶 🔷 🔸 🔹 🔺 🔻 �� 🔘 🔳 🔲\n🏁 - 🚩 🎌 🏴\n\nemoji reg/tags\n🇦🇨 🇦🇩 🇦🇪 🇦🇫 🇦 - 🇬 🇦🇮 🇦🇱 🇦🇲 🇦🇴 🇦🇶 🇦🇷 🇦🇸 🇦 - 🇹 🇦🇺 🇦🇼 🇦🇽 🇦🇿 🇧🇦 🇧🇧 🇧🇩 🇧 - 🇪\n��🇫 🇧🇬 🇧🇭 🇧🇮 🇧🇯 🇧🇱 🇧🇲 - 🇧🇳 🇧🇴 🇧🇶 🇧🇷 🇧🇸 🇧🇹 🇧🇻 🇧🇼 - ��🇾 🇧🇿 🇨🇦 🇨🇨 🇨🇩 🇨🇫\n🇨🇬 🇨🇭 - 🇨🇮 🇨🇰 🇨🇱 🇨🇲 🇨🇳 🇨🇴 🇨🇵 🇨🇷 - 🇨🇺 🇨🇻 🇨🇼 🇨🇽 🇨🇾 🇨🇿 🇩🇪 🇩🇬 - 🇩🇯 🇩🇰 🇩🇲\n🇩🇴 🇩🇿 🇪🇦 🇪🇨 🇪🇪 - 🇪🇬 🇪🇭 🇪🇷 🇪🇸 🇪🇹 🇪🇺 🇫🇮 🇫🇯 - 🇫🇰 🇫🇲 🇫🇴 🇫🇷 🇬🇦 🇬🇧 🇬🇩 🇬🇪\n - 🇬🇫 🇬🇬 🇬🇭 🇬🇮 🇬🇱 🇬🇲 🇬🇳 🇬🇵 - 🇬🇶 🇬🇷 🇬🇸 🇬�� 🇬🇺 🇬🇼 🇬🇾 🇭🇰 - 🇭🇲 🇭🇳 🇭🇷 🇭🇹 🇭🇺\n🇮🇨 🇮🇩 🇮🇪 - 🇮🇱 🇮🇲 🇮🇳 🇮🇴 🇮🇶 🇮🇷 🇮🇸 🇮🇹 - 🇯🇪 🇯🇲 🇯🇴 🇯🇵 🇰🇪 🇰🇬 🇰🇭 🇰🇮 - 🇰🇲 🇰🇳\n🇰🇵 🇰🇷 🇰🇼 🇰🇾 🇰🇿 🇱🇦 - 🇱🇧 🇱🇨 🇱🇮 🇱🇰 🇱🇷 🇱🇸 🇱🇹 🇱🇺 - ��🇻 🇱🇾 🇲🇦 🇲🇨 🇲🇩 🇲🇪 🇲🇫\n🇲🇬 - 🇲🇭 🇲🇰 🇲🇱 🇲🇲 🇲🇳 🇲🇴 🇲🇵 🇲🇶 - 🇲🇷 🇲🇸 🇲🇹 🇲🇺 🇲🇻 🇲🇼 🇲🇽 🇲🇾 - 🇲🇿 🇳🇦 🇳🇨 🇳🇪\n🇳🇫 🇳🇬 🇳🇮 🇳🇱 - 🇳🇴 🇳🇵 🇳🇷 🇳🇺 🇳🇿 🇴🇲 🇵🇦 🇵🇪 - 🇵🇫 🇵🇬 🇵🇭 🇵🇰 🇵🇱 🇵🇲 🇵🇳 🇵🇷 - 🇵🇸\n🇵🇹 🇵🇼 🇵🇾 🇶🇦 🇷🇪 🇷🇴 🇷🇸 - 🇷🇺 🇷🇼 🇸🇦 🇸�� 🇸🇨 🇸🇩 🇸🇪 🇸🇬 - 🇸🇭 🇸🇮 🇸🇯 🇸🇰 🇸🇱 🇸🇲\n🇸🇳 🇸🇴 - 🇸🇷 🇸🇸 🇸🇹 🇸🇻 🇸🇽 🇸🇾 🇸🇿 🇹🇦 - 🇹🇨 🇹🇩 🇹🇫 🇹🇬 🇹🇭 🇹🇯 🇹🇰 🇹🇱 - 🇹🇲 🇹🇳 🇹🇴\n🇹🇷 🇹🇹 🇹🇻 🇹🇼 🇹🇿 - 🇺🇦 🇺🇬 🇺🇲 🇺🇳 🇺🇸 🇺🇾 🇺🇿 🇻🇦 - ��🇨 🇻🇪 🇻🇬 🇻🇮 🇻🇳 🇻🇺 🇼🇫 🇼🇸\ - n🇽🇰 🇾🇪 🇾🇹 🇿🇦 🇿🇲 🇿🇼 🏴󠁧��󠁥 - 󠁮󠁧󠁿 🏴󠁧󠁢󠁳󠁣󠁴󠁿 🏴󠁧󠁢󠁷󠁬󠁳󠁿\n\n - modifier\n👋🏻 👋🏼 👋🏽 👋🏾 👋🏿 🤚🏻 🤚🏼 - 🤚🏽 🤚🏾 🤚🏿 🖐🏻 🖐🏼 🖐🏽 🖐🏾 🖐🏿 - ✋🏻 ✋🏼 ✋🏽 ✋🏾 ✋🏿 🖖🏻\n🖖🏼 🖖🏽 🖖 - 🏾 🖖🏿 👌🏻 👌🏼 👌🏽 👌🏾 👌🏿 🤌🏻 🤌 - 🏼 🤌🏽 🤌🏾 🤌🏿 ��🏻 🤏🏼 🤏🏽 🤏🏾 🤏 - 🏿 ✌🏻 ✌🏼\n✌🏽 ✌🏾 ✌🏿 🤞🏻 🤞🏼 🤞🏽 - 🤞🏾 🤞🏿 🤟🏻 🤟🏼 🤟🏽 🤟🏾 🤟🏿 🤘🏻 - 🤘🏼 🤘🏽 🤘🏾 🤘🏿 🤙🏻 🤙🏼 🤙🏽\n🤙🏾 - 🤙🏿 👈🏻 👈🏼 👈🏽 👈🏾 👈🏿 👉🏻 👉🏼 - 👉🏽 👉🏾 👉🏿 👆🏻 👆🏼 👆🏽 👆🏾 👆🏿 - 🖕🏻 🖕🏼 🖕🏽 🖕🏾\n🖕🏿 👇🏻 👇🏼 👇🏽 - 👇🏾 👇🏿 ☝🏻 ☝🏼 ☝🏽 ☝🏾 ☝🏿 👍🏻 👍� - � 👍🏽 👍🏾 👍🏿 👎🏻 👎🏼 👎🏽 👎🏾 👎 - 🏿\n✊🏻 ✊🏼 ✊🏽 ✊🏾 ✊🏿 👊🏻 👊🏼 👊🏽 - 👊🏾 👊🏿 🤛🏻 🤛🏼 🤛🏽 🤛🏾 🤛🏿 🤜🏻 - 🤜🏼 🤜🏽 🤜🏾 🤜🏿 👏🏻\n👏🏼 ��🏽 👏🏾 - 👏🏿 🙌🏻 🙌🏼 🙌🏽 🙌🏾 🙌🏿 👐🏻 👐🏼 - 👐🏽 👐🏾 👐🏿 🤲🏻 🤲🏼 ��🏽 🤲🏾 🤲🏿 - 🙏🏻 🙏🏼\n🙏🏽 🙏🏾 🙏🏿 ✍🏻 ✍🏼 ✍🏽 ✍ - 🏾 ✍🏿 💅🏻 💅🏼 💅🏽 💅🏾 💅🏿 🤳🏻 🤳 - 🏼 🤳🏽 🤳🏾 🤳🏿 💪🏻 💪🏼 💪🏽\n💪🏾 💪 - 🏿 🦵🏻 🦵🏼 🦵🏽 🦵🏾 🦵🏿 🦶🏻 🦶🏼 🦶 - 🏽 🦶🏾 🦶🏿 👂🏻 👂🏼 👂🏽 👂🏾 👂🏿 🦻 - 🏻 🦻🏼 🦻🏽 🦻🏾\n🦻🏿 👃🏻 👃🏼 👃🏽 👃 - 🏾 👃🏿 👶🏻 👶🏼 👶🏽 👶🏾 👶🏿 🧒🏻 🧒 - 🏼 🧒�� 🧒🏾 🧒🏿 👦🏻 👦🏼 👦🏽 👦🏾 👦 - 🏿\n👧🏻 👧🏼 👧🏽 👧🏾 👧🏿 🧑🏻 🧑🏼 🧑 - 🏽 🧑🏾 🧑🏿 👱🏻 👱🏼 👱🏽 👱🏾 👱🏿 👨 - 🏻 👨🏼 👨🏽 👨🏾 👨🏿 🧔🏻\n🧔🏼 ��🏽 - 🧔🏾 🧔🏿 👩🏻 👩🏼 👩🏽 👩🏾 👩🏿 🧓🏻 - 🧓🏼 🧓🏽 🧓🏾 🧓🏿 👴🏻 👴🏼 ��🏽 👴🏾 - 👴🏿 👵🏻 👵🏼\n👵🏽 👵🏾 👵🏿 🙍🏻 🙍🏼 - 🙍🏽 🙍🏾 🙍🏿 🙎🏻 🙎🏼 🙎🏽 🙎🏾 🙎🏿 - 🙅🏻 🙅🏼 🙅🏽 🙅🏾 🙅🏿 🙆🏻 🙆🏼 🙆🏽\n - 🙆🏾 🙆🏿 💁🏻 💁🏼 💁🏽 💁🏾 💁🏿 🙋🏻 - 🙋🏼 🙋🏽 🙋🏾 🙋🏿 🧏🏻 🧏🏼 🧏🏽 🧏🏾 - 🧏🏿 🙇🏻 🙇🏼 🙇🏽 🙇🏾\n🙇🏿 🤦🏻 🤦🏼 - 🤦🏽 🤦🏾 🤦🏿 🤷🏻 🤷🏼 🤷🏽 🤷🏾 🤷🏿 - 👮🏻 👮�� 👮🏽 👮🏾 👮🏿 🕵🏻 🕵🏼 🕵🏽 - 🕵🏾 🕵🏿\n💂🏻 💂🏼 💂🏽 💂🏾 💂🏿 🥷🏻 - 🥷🏼 🥷🏽 🥷🏾 🥷🏿 👷🏻 👷🏼 👷🏽 👷🏾 - 👷🏿 🤴🏻 🤴🏼 🤴🏽 🤴🏾 🤴🏿 👸🏻\n��🏼 - 👸🏽 👸🏾 👸🏿 👳🏻 👳🏼 👳🏽 👳🏾 👳🏿 - 👲🏻 👲🏼 👲🏽 👲🏾 👲🏿 🧕🏻 ��🏼 🧕🏽 - 🧕🏾 🧕🏿 🤵🏻 🤵🏼\n🤵🏽 🤵🏾 🤵🏿 👰🏻 - 👰🏼 👰🏽 👰🏾 👰🏿 🤰🏻 🤰🏼 🤰🏽 🤰🏾 - 🤰🏿 🤱🏻 🤱🏼 🤱🏽 🤱🏾 🤱🏿 👼🏻 👼🏼 - 👼🏽\n👼🏾 👼🏿 🎅🏻 🎅🏼 🎅🏽 🎅🏾 🎅🏿 - 🤶🏻 🤶🏼 🤶🏽 🤶🏾 🤶🏿 🦸🏻 🦸🏼 🦸🏽 - 🦸🏾 🦸🏿 🦹🏻 🦹🏼 🦹🏽 🦹🏾\n🦹🏿 🧙🏻 - 🧙🏼 🧙🏽 🧙🏾 🧙🏿 🧚🏻 🧚🏼 🧚🏽 🧚🏾 - 🧚🏿 🧛�� 🧛🏼 🧛🏽 🧛🏾 🧛🏿 🧜🏻 🧜🏼 - 🧜🏽 🧜🏾 🧜🏿\n🧝🏻 🧝🏼 🧝🏽 🧝🏾 🧝🏿 - 💆🏻 💆🏼 💆🏽 💆🏾 💆🏿 💇🏻 💇🏼 💇🏽 - 💇🏾 💇🏿 🚶🏻 🚶🏼 🚶🏽 🚶🏾 🚶🏿 🧍🏻\n - 🧍🏼 🧍🏽 🧍🏾 🧍🏿 🧎🏻 🧎🏼 🧎🏽 🧎🏾 - 🧎🏿 🏃🏻 🏃🏼 🏃🏽 🏃🏾 🏃🏿 ��🏻 💃🏼 - 💃🏽 💃🏾 💃🏿 🕺🏻 🕺🏼\n🕺🏽 🕺🏾 🕺🏿 - 🕴🏻 🕴🏼 🕴🏽 🕴🏾 🕴🏿 🧖🏻 🧖🏼 🧖🏽 - 🧖🏾 🧖🏿 🧗🏻 🧗🏼 🧗🏽 🧗🏾 🧗🏿 🏇🏻 - 🏇🏼 🏇🏽\n🏇🏾 🏇🏿 🏂🏻 🏂🏼 🏂🏽 🏂🏾 - 🏂🏿 🏌🏻 🏌🏼 🏌🏽 🏌🏾 🏌🏿 🏄🏻 🏄🏼 - 🏄🏽 🏄🏾 🏄🏿 🚣🏻 🚣🏼 🚣🏽 🚣🏾\n🚣🏿 - 🏊🏻 🏊🏼 🏊🏽 🏊🏾 🏊🏿 ⛹🏻 ⛹🏼 ⛹🏽 ⛹ - 🏾 ⛹🏿 🏋�� 🏋🏼 🏋🏽 🏋🏾 🏋🏿 🚴🏻 🚴 - 🏼 🚴🏽 🚴🏾 🚴🏿\n🚵🏻 🚵🏼 🚵🏽 🚵🏾 🚵 - 🏿 🤸🏻 🤸🏼 🤸🏽 🤸🏾 🤸🏿 🤽🏻 🤽🏼 🤽 - 🏽 🤽🏾 🤽🏿 🤾🏻 🤾🏼 🤾🏽 🤾🏾 🤾🏿 🤹 - 🏻\n🤹🏼 🤹🏽 🤹🏾 🤹🏿 🧘🏻 🧘🏼 🧘🏽 🧘 - 🏾 🧘🏿 🛀🏻 🛀🏼 🛀🏽 🛀🏾 🛀🏿 ��🏻 🛌 - 🏼 🛌🏽 🛌🏾 🛌🏿 👭🏻 👭🏼\n👭🏽 👭🏾 👭 - 🏿 👫🏻 👫🏼 👫🏽 👫🏾 👫🏿 👬🏻 👬🏼 👬 - 🏽 👬🏾 👬🏿 🏻 🏼 🏽 🏾 🏿\n\nzwj emoji\n👁️‍ - 🗨️ 👨‍🦰 👨🏻‍🦰 👨🏼‍🦰 👨🏽‍🦰 👨 - 🏾‍🦰 👨🏿‍🦰 👨‍🦱 👨🏻‍🦱 👨🏼‍🦱 - 👨🏽‍🦱 👨🏾‍🦱 👨🏿‍🦱 👨‍🦳 👨🏻‍ - 🦳 👨🏼‍🦳 👨🏽‍🦳 👨🏾‍🦳 👨🏿‍🦳 👨 - ‍🦲 👨🏻‍🦲\n👨🏼‍🦲 👨🏽‍🦲 👨🏾‍🦲 - 👨🏿‍🦲 👩‍🦰 👩🏻‍🦰 👩🏼‍🦰 👩🏽‍ - 🦰 👩🏾‍🦰 👩🏿‍🦰 🧑‍🦰 ��🏻‍🦰 🧑 - 🏼‍🦰 🧑🏽‍🦰 🧑🏾‍🦰 🧑🏿‍🦰 👩‍🦱 - 👩🏻‍🦱 👩🏼‍🦱 👩🏽‍🦱 👩🏾‍��\n👩 - 🏿‍🦱 🧑‍🦱 🧑🏻‍🦱 🧑🏼‍🦱 🧑🏽‍🦱 - 🧑🏾‍🦱 🧑🏿‍🦱 👩‍🦳 👩🏻‍🦳 👩🏼‍ - 🦳 👩🏽‍🦳 👩🏾‍🦳 👩🏿‍🦳 🧑‍🦳 🧑🏻 - ‍🦳 🧑🏼‍🦳 🧑🏽‍🦳 🧑🏾‍🦳 🧑🏿‍🦳 - ��‍🦲 👩🏻‍🦲\n👩🏼‍🦲 👩🏽‍🦲 👩🏾‍ - 🦲 👩🏿‍🦲 🧑‍🦲 🧑🏻‍🦲 🧑🏼‍🦲 🧑🏽 - ‍🦲 🧑🏾‍🦲 🧑🏿‍🦲 👱‍♀️ 👱🏻‍♀️ - 👱🏼‍♀️ 👱🏽‍♀️ 👱🏾‍♀️ 👱🏿‍♀️ - 👱‍♂️ 👱🏻‍♂️ 👱🏼‍♂️ 👱🏽‍♂️ 👱 - 🏾‍♂️\n👱🏿‍♂️ 🙍‍♂️ 🙍🏻‍♂️ 🙍🏼 - ‍♂️ 🙍🏽‍♂️ 🙍🏾‍♂️ 🙍🏿‍♂️ 🙍‍♀ - ️ 🙍🏻‍♀️ 🙍🏼‍♀️ 🙍🏽‍♀️ 🙍🏾‍♀ - ️ 🙍🏿‍♀️ 🙎‍♂️ 🙎🏻‍♂️ 🙎🏼‍♂️ - ��🏽‍♂️ 🙎🏾‍♂️ 🙎🏿‍♂️ 🙎‍♀️ 🙎 - 🏻‍♀️\n🙎🏼‍♀️ 🙎🏽‍♀️ 🙎🏾‍♀️ 🙎 - 🏿‍♀️ 🙅‍♂️ 🙅🏻‍♂️ 🙅🏼‍♂️ 🙅🏽 - ‍♂️ 🙅🏾‍♂️ 🙅🏿‍♂️ 🙅‍♀️ 🙅🏻‍♀ - ️ 🙅🏼‍♀️ 🙅🏽‍♀️ 🙅🏾‍♀️ 🙅🏿‍♀ - ️ 🙆‍♂️ 🙆🏻‍♂️ 🙆🏼‍♂️ 🙆🏽‍♂️ - 🙆🏾‍♂️\n🙆🏿‍♂️ 🙆‍♀️ 🙆🏻‍♀️ 🙆 - 🏼‍♀️ 🙆🏽‍♀️ 🙆🏾‍♀️ 🙆🏿‍♀️ 💁 - ‍♂️ 💁🏻‍♂️ 💁🏼‍♂️ 💁🏽‍♂️ 💁🏾 - ‍♂️ 💁🏿‍♂️ 💁‍♀️ 💁🏻‍♀️ 💁🏼‍♀ - ️ 💁🏽‍♀️ 💁🏾‍♀️ 💁🏿‍♀️ 🙋‍♂️ - 🙋🏻‍♂️\n🙋🏼‍♂️ 🙋🏽‍♂️ 🙋🏾‍♂️ - 🙋🏿‍♂️ 🙋‍♀️ 🙋🏻‍♀️ 🙋🏼‍♀️ 🙋 - 🏽‍♀️ 🙋🏾‍♀️ 🙋🏿‍♀️ 🧏‍♂️ 🧏🏻 - ‍♂️ 🧏🏼‍♂️ 🧏🏽‍♂️ 🧏🏾‍♂️ 🧏🏿 - ‍♂️ 🧏‍♀️ 🧏🏻‍♀️ 🧏🏼‍♀️ 🧏🏽‍♀ - ️ 🧏🏾‍♀️\n🧏🏿‍♀️ 🙇‍♂️ 🙇🏻‍♂️ - 🙇🏼‍♂️ 🙇🏽‍♂️ 🙇🏾‍♂️ 🙇🏿‍♂️ - 🙇‍♀️ 🙇🏻‍♀️ 🙇🏼‍♀️ 🙇🏽‍♀️ 🙇 - 🏾‍♀️ 🙇🏿‍♀️ 🤦‍♂️ 🤦🏻‍♂️ 🤦🏼 - ‍♂️ 🤦🏽‍♂️ 🤦🏾‍♂️ 🤦🏿‍♂️ 🤦‍♀ - ️ 🤦🏻‍♀️\n🤦🏼‍♀️ 🤦🏽‍♀️ 🤦🏾‍♀ - ️ 🤦🏿‍♀️ 🤷‍♂️ 🤷🏻‍♂️ 🤷🏼‍♂️ - 🤷🏽‍♂️ 🤷🏾‍♂️ 🤷🏿‍♂️ 🤷‍♀️ 🤷 - 🏻‍♀️ 🤷🏼‍♀️ 🤷🏽‍♀️ 🤷🏾‍♀️ 🤷 - 🏿‍♀️ 🧑‍⚕️ 🧑🏻‍⚕️ ��🏼‍⚕️ 🧑🏽 - ‍⚕️ 🧑🏾‍⚕️\n🧑🏿‍⚕️ 👨‍⚕️ 👨🏻‍ - ⚕️ 👨🏼‍⚕️ 👨🏽‍⚕️ 👨🏾‍⚕️ 👨🏿‍ - ⚕️ 👩‍⚕️ 👩🏻‍⚕️ 👩🏼‍⚕️ 👩🏽‍⚕️ - 👩🏾‍⚕️ 👩🏿‍⚕️ 🧑‍🎓 🧑🏻‍🎓 🧑🏼 - ‍🎓 🧑🏽‍🎓 🧑🏾‍🎓 🧑🏿‍🎓 👨‍🎓 👨 - 🏻‍🎓\n👨🏼‍🎓 👨🏽‍🎓 👨🏾‍🎓 👨🏿‍ - 🎓 👩‍🎓 👩🏻‍🎓 👩🏼‍🎓 👩🏽‍🎓 👩🏾 - ‍🎓 👩🏿‍🎓 🧑‍🏫 🧑🏻‍🏫 🧑🏼‍🏫 🧑 - 🏽‍🏫 🧑🏾‍🏫 🧑🏿‍🏫 👨‍🏫 👨🏻‍🏫 - 👨🏼‍🏫 👨🏽‍🏫 👨🏾‍🏫\n👨🏿‍🏫 👩‍ - 🏫 👩🏻‍🏫 👩🏼‍🏫 👩🏽‍🏫 👩🏾‍🏫 👩 - 🏿‍🏫 🧑‍⚖️ 🧑🏻‍⚖️ 🧑🏼‍⚖️ 🧑🏽‍ - ⚖️ 🧑🏾‍⚖️ 🧑🏿‍⚖️ 👨‍⚖️ 👨🏻‍⚖️ - 👨🏼‍⚖️ 👨🏽‍⚖️ 👨🏾‍⚖️ 👨🏿‍⚖️ - 👩‍⚖️ 👩🏻‍⚖️\n👩🏼‍⚖️ 👩🏽‍⚖️ 👩 - 🏾‍⚖️ 👩🏿‍⚖️ 🧑‍🌾 🧑🏻‍🌾 🧑🏼‍ - 🌾 🧑🏽‍🌾 🧑🏾‍🌾 🧑🏿‍🌾 👨‍🌾 👨🏻 - ‍🌾 👨🏼‍🌾 👨🏽‍🌾 👨🏾‍🌾 👨🏿‍🌾 - 👩‍🌾 👩🏻‍🌾 👩🏼‍🌾 👩🏽‍🌾 👩🏾‍ - 🌾\n👩��‍🌾 🧑‍🍳 🧑🏻‍🍳 🧑🏼‍🍳 🧑 - 🏽‍🍳 🧑🏾‍🍳 🧑🏿‍🍳 👨‍🍳 👨🏻‍🍳 - 👨🏼‍🍳 👨🏽‍🍳 👨🏾‍🍳 👨🏿‍🍳 👩‍ - 🍳 👩🏻‍🍳 👩🏼‍🍳 👩🏽‍🍳 👩🏾‍🍳 👩 - 🏿‍🍳 🧑‍🔧 ��🏻‍🔧\n🧑🏼‍🔧 🧑🏽‍🔧 - 🧑🏾‍🔧 🧑🏿‍🔧 👨‍🔧 👨🏻‍🔧 👨🏼‍ - 🔧 👨🏽‍🔧 👨🏾‍🔧 👨🏿‍🔧 👩‍🔧 👩🏻 - ‍🔧 👩🏼‍🔧 👩🏽‍🔧 👩🏾‍🔧 👩🏿‍🔧 - 🧑‍🏭 🧑🏻‍🏭 🧑🏼‍🏭 🧑🏽‍🏭 🧑🏾‍ - 🏭\n🧑🏿‍🏭 👨‍🏭 👨🏻‍🏭 👨🏼‍🏭 👨🏽 - ‍🏭 👨🏾‍🏭 👨🏿‍🏭 👩‍🏭 👩🏻‍🏭 👩 - 🏼‍🏭 👩🏽‍🏭 👩🏾‍🏭 👩🏿‍🏭 🧑‍💼 - 🧑🏻‍💼 🧑🏼‍💼 🧑🏽‍💼 🧑🏾‍💼 🧑🏿 - ‍💼 👨‍💼 👨🏻‍💼\n👨🏼‍💼 👨🏽‍💼 👨 - 🏾‍💼 👨🏿‍💼 👩‍💼 👩🏻‍💼 👩🏼‍💼 - 👩🏽‍💼 👩🏾‍💼 👩🏿‍💼 🧑‍🔬 🧑🏻‍ - 🔬 🧑🏼‍🔬 🧑🏽‍🔬 🧑🏾‍🔬 🧑🏿‍🔬 👨 - ‍🔬 👨🏻‍🔬 👨🏼‍🔬 👨🏽‍🔬 👨🏾‍🔬\n - 👨🏿‍🔬 👩‍🔬 👩🏻‍🔬 👩🏼‍🔬 👩🏽‍ - 🔬 👩🏾‍🔬 👩🏿‍🔬 🧑‍💻 🧑🏻‍💻 🧑🏼 - ‍💻 🧑🏽‍💻 🧑🏾‍💻 🧑🏿‍💻 👨‍💻 👨 - 🏻‍💻 👨🏼‍💻 👨🏽‍💻 👨🏾‍💻 👨🏿‍ - 💻 👩‍💻 👩🏻‍💻\n👩🏼‍💻 👩🏽‍💻 👩� - �‍💻 👩🏿‍💻 🧑‍🎤 🧑🏻‍🎤 🧑🏼‍🎤 - 🧑🏽‍🎤 🧑🏾‍🎤 🧑🏿‍🎤 👨‍🎤 👨🏻‍ - 🎤 👨🏼‍🎤 👨🏽‍🎤 👨🏾‍🎤 👨🏿‍🎤 👩 - ‍🎤 👩🏻‍🎤 👩🏼‍🎤 👩🏽‍🎤 👩🏾‍🎤\n - 👩🏿‍�� 🧑‍🎨 🧑🏻‍🎨 🧑🏼‍🎨 🧑🏽‍ - 🎨 🧑🏾‍🎨 🧑🏿‍🎨 👨‍🎨 👨🏻‍🎨 👨🏼 - ‍🎨 👨🏽‍🎨 👨🏾‍🎨 👨🏿‍🎨 👩‍🎨 👩 - 🏻‍🎨 👩🏼‍🎨 👩🏽‍🎨 👩🏾‍🎨 👩🏿‍ - 🎨 🧑‍✈️ 🧑🏻‍✈️\n🧑🏼‍✈️ 🧑🏽‍✈️ - 🧑🏾‍✈️ 🧑🏿‍✈️ 👨‍✈️ 👨🏻‍✈️ 👨 - 🏼‍✈️ 👨🏽‍✈️ 👨🏾‍✈️ 👨🏿‍✈️ 👩 - ‍✈️ 👩🏻‍✈️ 👩🏼‍✈️ 👩🏽‍✈️ 👩🏾 - ‍✈️ 👩🏿‍✈️ 🧑‍🚀 🧑🏻‍🚀 🧑🏼‍🚀 - 🧑🏽‍🚀 🧑🏾‍🚀\n🧑🏿‍🚀 👨‍🚀 👨🏻‍ - 🚀 👨🏼‍🚀 👨🏽‍🚀 👨🏾‍🚀 👨🏿‍🚀 👩 - ‍🚀 👩🏻‍🚀 👩🏼‍🚀 👩🏽‍🚀 👩🏾‍🚀 - 👩🏿‍🚀 🧑‍🚒 🧑🏻‍🚒 🧑🏼‍🚒 🧑🏽‍ - 🚒 🧑🏾‍🚒 🧑🏿‍🚒 👨‍🚒 👨🏻‍🚒\n👨🏼 - ‍🚒 👨🏽‍🚒 👨🏾‍🚒 👨🏿‍🚒 👩‍🚒 👩 - 🏻‍🚒 👩🏼‍🚒 👩🏽‍🚒 👩🏾‍🚒 👩🏿‍ - 🚒 👮‍♂️ 👮🏻‍♂️ 👮🏼‍♂️ 👮🏽‍♂️ - 👮🏾‍♂️ 👮🏿‍♂️ 👮‍♀️ 👮🏻‍♀️ 👮 - 🏼‍♀️ 👮🏽‍♀️ 👮🏾‍♀️\n👮🏿‍♀️ 🕵 - ️‍♂️ 🕵🏻‍♂️ 🕵🏼‍♂️ 🕵🏽‍♂️ 🕵 - 🏾‍♂️ 🕵🏿‍♂️ 🕵️‍♀️ 🕵🏻‍♀️ 🕵 - 🏼‍♀️ 🕵🏽‍♀️ 🕵🏾‍♀️ 🕵🏿‍♀️ 💂 - ‍♂️ 💂🏻‍♂️ 💂🏼‍♂️ 💂🏽‍♂️ 💂🏾 - ‍♂️ 💂🏿‍♂️ 💂‍♀️ 💂🏻‍♀️\n💂🏼‍ - ♀️ 💂🏽‍♀️ 💂🏾‍♀️ 💂🏿‍♀️ 👷‍♂️ - 👷🏻‍♂️ 👷🏼‍♂️ 👷🏽‍♂️ 👷🏾‍♂️ - 👷🏿‍♂️ 👷‍♀️ 👷🏻‍♀️ 👷��‍♀️ 👷 - 🏽‍♀️ 👷🏾‍♀️ 👷🏿‍♀️ 👳‍♂️ 👳🏻 - ‍♂️ 👳🏼‍♂️ 👳🏽‍♂️ 👳🏾‍♂️\n👳🏿 - ‍♂️ 👳‍♀️ 👳🏻‍♀️ 👳🏼‍♀️ 👳🏽‍♀ - ️ 👳🏾‍♀️ 👳🏿‍♀️ 🤵‍♂️ 🤵🏻‍♂️ - 🤵🏼‍♂️ 🤵🏽‍♂️ 🤵🏾‍♂️ 🤵🏿‍♂️ - 🤵‍♀️ 🤵🏻‍♀️ 🤵🏼‍♀️ 🤵🏽‍♀️ 🤵 - 🏾‍♀️ 🤵🏿‍♀️ 👰‍♂️ 👰🏻‍♂️\n👰🏼 - ‍♂️ 👰🏽‍♂️ 👰🏾‍♂️ 👰🏿‍♂️ 👰‍♀ - ️ 👰🏻‍♀️ 👰🏼‍♀️ 👰🏽‍♀️ 👰🏾‍♀ - ️ ��🏿‍♀️ 👩‍🍼 👩🏻‍🍼 👩🏼‍🍼 👩 - 🏽‍🍼 👩🏾‍🍼 👩🏿‍🍼 👨‍🍼 👨🏻‍🍼 - 👨🏼‍🍼 👨🏽‍🍼 👨🏾‍🍼\n👨🏿‍🍼 🧑‍ - 🍼 🧑🏻‍🍼 🧑🏼‍🍼 🧑🏽‍🍼 🧑🏾‍🍼 🧑 - 🏿‍🍼 🧑‍�� 🧑🏻‍🎄 🧑🏼‍🎄 🧑🏽‍🎄 - 🧑🏾‍🎄 🧑🏿‍🎄 🦸‍♂️ 🦸🏻‍♂️ 🦸🏼 - ‍♂️ 🦸🏽‍♂️ 🦸��‍♂️ 🦸🏿‍♂️ 🦸‍ - ♀️ 🦸🏻‍♀️\n🦸🏼‍♀️ 🦸🏽‍♀️ 🦸🏾‍ - ♀️ 🦸🏿‍♀️ 🦹‍♂️ 🦹🏻‍♂️ ��🏼‍♂ - ️ 🦹🏽‍♂️ 🦹🏾‍♂️ 🦹🏿‍♂️ 🦹‍♀️ - 🦹🏻‍♀️ 🦹🏼‍♀️ 🦹🏽‍♀️ 🦹🏾‍♀️ - 🦹🏿‍♀️ 🧙‍♂️ 🧙🏻‍♂️ 🧙🏼‍♂️ 🧙 - 🏽‍♂️ 🧙🏾‍♂️\n🧙🏿‍♂️ 🧙‍♀️ 🧙🏻 - ‍♀️ 🧙🏼‍♀️ 🧙🏽‍♀️ 🧙🏾‍♀️ 🧙🏿 - ‍♀️ 🧚‍♂️ 🧚🏻‍♂️ 🧚🏼‍♂️ 🧚🏽‍♂ - ️ 🧚🏾‍♂️ 🧚🏿‍♂️ 🧚‍♀️ 🧚��‍♀️ - 🧚🏼‍♀️ 🧚🏽‍♀️ 🧚🏾‍♀️ 🧚🏿‍♀️ - 🧛‍♂️ 🧛🏻‍♂️\n🧛🏼‍♂️ 🧛🏽‍♂️ 🧛 - 🏾‍♂️ 🧛🏿‍♂️ 🧛‍♀️ 🧛🏻‍♀️ 🧛🏼 - ‍♀️ 🧛🏽‍♀️ 🧛🏾‍♀️ 🧛🏿‍♀️ 🧜‍♂ - ️ 🧜🏻‍♂️ 🧜🏼‍♂️ 🧜🏽‍♂️ 🧜🏾‍♂ - ️ 🧜🏿‍♂️ 🧜‍♀️ 🧜🏻‍♀️ 🧜🏼‍♀️ - 🧜🏽‍♀️ 🧜🏾‍♀️\n🧜🏿‍♀️ 🧝‍♂️ 🧝 - 🏻‍♂️ 🧝🏼‍♂️ 🧝🏽‍♂️ 🧝🏾‍♂️ 🧝 - 🏿‍♂️ 🧝‍♀️ 🧝🏻‍♀️ 🧝🏼‍♀️ 🧝🏽 - ‍♀️ ��🏾‍♀️ 🧝🏿‍♀️ 🧞‍♂️ 🧞‍♀ - ️ 🧟‍♂️ 🧟‍♀️ 💆‍♂️ 💆🏻‍♂️ 💆🏼 - ‍♂️ 💆🏽‍♂️\n💆🏾‍♂️ 💆🏿‍♂️ 💆‍ - ♀️ 💆🏻‍♀️ 💆🏼‍♀️ 💆🏽‍♀️ 💆🏾‍ - ♀️ 💆🏿‍♀️ 💇‍♂️ 💇🏻‍♂️ 💇🏼‍♂️ - 💇🏽‍♂️ 💇🏾‍♂️ 💇🏿‍♂️ 💇‍♀️ 💇 - 🏻‍♀️ 💇🏼‍♀️ 💇🏽‍♀️ 💇🏾‍♀️ 💇 - 🏿‍♀️ ��‍♂️\n🚶🏻‍♂️ 🚶🏼‍♂️ 🚶 - 🏽‍♂️ 🚶🏾‍♂️ 🚶🏿‍♂️ 🚶‍♀️ 🚶🏻 - ‍♀️ 🚶🏼‍♀️ 🚶🏽‍♀️ 🚶🏾‍♀️ 🚶🏿 - ‍♀️ 🧍‍♂️ 🧍🏻‍♂️ 🧍🏼‍♂️ 🧍🏽‍♂ - ️ 🧍🏾‍♂️ 🧍🏿‍♂️ 🧍‍♀️ 🧍🏻‍♀️ - 🧍🏼‍♀️ 🧍🏽‍♀️\n🧍🏾‍♀️ 🧍🏿‍♀️ - 🧎‍♂️ 🧎🏻‍♂️ 🧎🏼‍♂️ 🧎🏽‍♂️ 🧎 - 🏾‍♂️ 🧎🏿‍♂️ 🧎‍♀️ 🧎🏻‍♀️ 🧎🏼 - ‍♀️ 🧎🏽‍♀️ 🧎🏾‍♀️ 🧎🏿‍♀️ 🧑‍ - 🦯 🧑🏻‍🦯 🧑🏼‍🦯 ��🏽‍🦯 🧑🏾‍🦯 - 🧑🏿‍🦯 👨‍🦯\n👨🏻‍🦯 👨🏼‍🦯 👨🏽‍ - 🦯 👨🏾‍🦯 👨🏿‍🦯 👩‍🦯 👩🏻‍🦯 👩🏼 - ‍🦯 👩🏽‍🦯 👩🏾‍🦯 👩🏿‍🦯 🧑‍🦼 🧑 - 🏻‍🦼 🧑🏼‍🦼 🧑🏽‍🦼 🧑🏾‍🦼 🧑🏿‍ - 🦼 👨‍🦼 👨🏻‍🦼 👨🏼‍🦼 👨🏽‍🦼\n👨🏾 - ‍🦼 👨🏿‍🦼 👩‍🦼 👩🏻‍🦼 👩🏼‍🦼 👩 - 🏽‍🦼 👩🏾‍🦼 👩🏿‍🦼 🧑‍🦽 🧑🏻‍🦽 - 🧑🏼‍🦽 🧑🏽‍🦽 🧑🏾‍🦽 🧑🏿‍🦽 👨‍ - 🦽 👨🏻‍🦽 👨🏼‍🦽 👨🏽‍🦽 👨🏾‍🦽 👨 - 🏿‍🦽 👩‍🦽\n👩🏻‍🦽 👩🏼‍🦽 👩🏽‍🦽 - 👩🏾‍🦽 👩🏿‍🦽 🏃‍♂️ 🏃🏻‍♂️ 🏃🏼 - ‍♂️ 🏃🏽‍♂️ 🏃🏾‍♂️ 🏃🏿‍♂️ 🏃‍♀ - ️ 🏃🏻‍♀️ 🏃🏼‍♀️ 🏃🏽‍♀️ 🏃🏾‍♀ - ️ 🏃🏿‍♀️ 👯‍♂️ 👯‍♀️ 🧖‍♂️ 🧖🏻 - ‍♂️\n🧖🏼‍♂️ 🧖🏽‍♂️ 🧖🏾‍♂️ 🧖🏿 - ‍♂️ 🧖‍♀️ 🧖🏻‍♀️ 🧖🏼‍♀️ 🧖🏽‍♀ - ️ 🧖🏾‍♀️ 🧖🏿‍♀️ 🧗‍♂️ 🧗��‍♂️ - 🧗🏼‍♂️ 🧗🏽‍♂️ 🧗🏾‍♂️ 🧗🏿‍♂️ - 🧗‍♀️ 🧗🏻‍♀️ 🧗🏼‍♀️ 🧗🏽‍♀️ 🧗 - 🏾‍♀️\n🧗🏿‍♀️ 🏌️‍♂️ 🏌🏻‍♂️ 🏌 - 🏼‍♂️ 🏌🏽‍♂️ 🏌🏾‍♂️ 🏌🏿‍♂️ 🏌 - ️‍♀️ 🏌🏻‍♀️ 🏌🏼‍♀️ 🏌🏽‍♀️ 🏌 - 🏾‍♀️ 🏌🏿‍♀️ 🏄‍♂️ 🏄🏻‍♂️ 🏄🏼 - ‍♂️ 🏄🏽‍♂️ 🏄🏾‍♂️ 🏄🏿‍♂️ ��‍ - ♀️ 🏄🏻‍♀️\n🏄🏼‍♀️ 🏄🏽‍♀️ 🏄🏾‍ - ♀️ 🏄🏿‍♀️ 🚣‍♂️ 🚣🏻‍♂️ 🚣🏼‍♂️ - 🚣🏽‍♂️ 🚣🏾‍♂️ 🚣🏿‍♂️ 🚣‍♀️ 🚣 - 🏻‍♀️ 🚣🏼‍♀️ 🚣🏽‍♀️ 🚣🏾‍♀️ 🚣 - 🏿‍♀️ 🏊‍♂️ 🏊🏻‍♂️ 🏊🏼‍♂️ 🏊🏽 - ‍♂️ 🏊🏾‍♂️\n🏊🏿‍♂️ 🏊‍♀️ 🏊🏻‍ - ♀️ 🏊🏼‍♀️ 🏊🏽‍♀️ 🏊🏾‍♀️ 🏊🏿‍ - ♀️ ⛹️‍♂️ ⛹🏻‍♂️ ⛹🏼‍♂️ ⛹🏽‍♂️ - ⛹🏾‍♂️ ⛹🏿‍♂️ ⛹️‍♀️ ⛹🏻‍♀️ ⛹🏼 - ‍♀️ ⛹🏽‍♀️ ⛹🏾‍♀️ ⛹🏿‍♀️ 🏋️‍♂ - ️ 🏋🏻‍♂️\n🏋🏼‍♂️ 🏋🏽‍♂️ 🏋🏾‍♂ - ️ 🏋🏿‍♂️ 🏋️‍♀️ 🏋🏻‍♀️ 🏋🏼‍♀️ - 🏋🏽‍♀️ 🏋🏾‍♀️ 🏋🏿‍♀️ 🚴‍♂️ 🚴 - 🏻‍♂️ 🚴🏼‍♂️ 🚴🏽‍♂️ 🚴🏾‍♂️ 🚴 - 🏿‍♂️ 🚴‍♀️ 🚴🏻‍♀️ 🚴🏼‍♀️ 🚴🏽 - ‍♀️ 🚴🏾‍♀️\n🚴🏿‍♀️ 🚵‍♂️ 🚵🏻‍ - ♂️ 🚵🏼‍♂️ 🚵🏽‍♂️ 🚵🏾‍♂️ 🚵🏿‍ - ♂️ 🚵‍♀️ 🚵🏻‍♀️ 🚵🏼‍♀️ 🚵🏽‍♀️ - 🚵🏾‍♀️ 🚵🏿‍♀️ 🤸‍♂️ ��🏻‍♂️ - 🤸🏼‍♂️ 🤸🏽‍♂️ 🤸🏾‍♂️ 🤸🏿‍♂️ - 🤸‍♀️ 🤸🏻‍♀️\n🤸🏼‍♀️ 🤸🏽‍♀️ 🤸 - 🏾‍♀️ 🤸🏿‍♀️ 🤼‍♂️ 🤼‍♀️ 🤽‍♂️ - 🤽🏻‍♂️ 🤽🏼‍♂️ 🤽🏽‍♂️ 🤽🏾‍♂️ - 🤽🏿‍♂️ 🤽‍♀️ 🤽🏻‍♀️ 🤽🏼‍♀️ 🤽 - 🏽‍♀️ 🤽🏾‍♀️ 🤽🏿‍♀️ 🤾‍♂️ 🤾🏻 - ‍♂️ 🤾🏼‍♂️\n🤾🏽‍♂️ 🤾🏾‍♂️ 🤾🏿 - ‍♂️ 🤾‍♀️ 🤾🏻‍♀️ 🤾🏼‍♀️ 🤾🏽‍♀ - ️ 🤾🏾‍♀️ 🤾🏿‍♀️ 🤹‍♂️ 🤹🏻‍♂️ - 🤹��‍♂️ 🤹🏽‍♂️ 🤹🏾‍♂️ 🤹🏿‍♂️ - 🤹‍♀️ 🤹🏻‍♀️ 🤹🏼‍♀️ 🤹🏽‍♀️ 🤹 - 🏾‍♀️ 🤹🏿‍♀️\n🧘‍♂️ 🧘🏻‍♂️ 🧘🏼 - ‍♂️ 🧘🏽‍♂️ 🧘🏾‍♂️ 🧘🏿‍♂️ 🧘‍♀ - ️ 🧘🏻‍♀️ 🧘🏼‍♀️ 🧘🏽‍♀️ 🧘🏾‍♀ - ️ 🧘🏿‍♀️ 🧑‍🤝‍🧑 🧑🏻‍🤝‍🧑🏻 🧑 - 🏻‍🤝‍🧑🏼 🧑🏻‍🤝‍🧑🏽 🧑🏻‍🤝‍�� - 🏾 🧑🏻‍🤝‍🧑🏿 🧑🏼‍🤝‍🧑🏻 🧑🏼‍🤝 - ‍🧑🏼 🧑🏼‍🤝‍🧑🏽\n🧑🏼‍🤝‍🧑🏾 🧑 - 🏼‍🤝‍🧑🏿 🧑🏽‍🤝‍🧑🏻 🧑🏽‍🤝‍🧑 - 🏼 🧑🏽‍🤝‍🧑🏽 🧑🏽‍🤝‍🧑🏾 🧑🏽‍🤝 - ‍🧑🏿 🧑🏾‍🤝‍🧑🏻 🧑🏾‍🤝‍🧑🏼 🧑🏾 - ‍🤝‍🧑🏽 🧑🏾‍🤝‍🧑🏾 🧑🏾‍🤝‍🧑🏿 - 🧑🏿‍🤝‍🧑🏻 🧑🏿‍🤝‍🧑🏼 🧑🏿‍🤝‍ - 🧑🏽 🧑🏿‍🤝‍🧑🏾 🧑🏿‍🤝‍🧑🏿 👩🏻‍ - 🤝‍👩🏼 👩🏻‍🤝‍��🏽 👩🏻‍🤝‍👩🏾 - 👩🏻‍🤝‍👩🏿\n👩🏼‍🤝‍👩🏻 👩🏼‍🤝‍ - 👩🏽 👩🏼‍🤝‍👩🏾 👩🏼‍🤝‍👩🏿 👩🏽‍ - 🤝‍👩🏻 👩🏽‍🤝‍👩🏼 👩🏽‍🤝‍👩🏾 👩 - 🏽‍🤝‍👩🏿 👩🏾‍🤝‍👩🏻 👩🏾‍🤝‍👩 - 🏼 👩🏾‍🤝‍👩🏽 👩🏾‍🤝‍👩🏿 👩🏿‍🤝 - ‍👩🏻 👩🏿‍🤝‍👩🏼 👩🏿‍🤝‍👩🏽 👩🏿 - ‍🤝‍👩🏾 👩🏻‍🤝‍👨🏼 👩🏻‍🤝‍👨🏽 - 👩🏻‍🤝‍👨🏾 👩🏻‍🤝‍👨🏿 👩🏼‍🤝‍ - ��🏻\n👩🏼‍🤝‍👨🏽 👩🏼‍🤝‍👨🏾 👩🏼 - ‍🤝‍👨🏿 👩🏽‍🤝‍👨🏻 👩🏽‍🤝‍👨🏼 - 👩🏽‍🤝‍👨🏾 👩🏽‍🤝‍👨🏿 👩🏾‍🤝‍ - 👨🏻 👩🏾‍🤝‍👨🏼 👩🏾‍🤝‍👨🏽 👩🏾‍ - 🤝‍👨🏿 👩🏿‍🤝‍👨🏻 👩🏿‍🤝‍👨🏼 👩 - 🏿‍🤝‍👨🏽 👩🏿‍🤝‍👨🏾 👨🏻‍🤝‍👨 - 🏼 👨🏻‍🤝‍👨🏽 👨🏻‍🤝‍👨🏾 👨🏻‍🤝 - ‍👨🏿 👨🏼‍🤝‍👨🏻 👨🏼‍🤝‍👨🏽\n👨 - 🏼‍🤝‍👨🏾 👨🏼‍🤝‍👨🏿 👨🏽‍🤝‍👨 - 🏻 👨🏽‍🤝‍👨🏼 👨🏽‍🤝‍👨🏾 👨🏽‍🤝 - ‍👨🏿 👨🏾‍🤝‍👨🏻 👨🏾‍🤝‍👨🏼 👨🏾 - ‍🤝‍👨🏽 👨🏾‍🤝‍👨🏿 👨🏿‍🤝‍👨🏻 - 👨🏿‍🤝‍👨🏼 👨🏿‍🤝‍👨🏽 👨🏿‍🤝‍ - 👨🏾 👩‍❤️‍💋‍👨 👨‍❤️‍💋‍👨 👩‍ - ❤️‍💋‍👩 👩‍❤️‍👨 👨‍❤️‍👨 👩‍❤ - ️‍👩 ��‍👩‍👦\n👨‍👩‍👧 👨‍👩‍👧‍ - 👦 👨‍👩‍👦‍👦 👨‍👩‍👧‍👧 👨‍👨‍ - 👦 👨‍👨‍👧 👨‍👨‍👧‍👦 👨‍👨‍👦‍ - 👦 👨‍👨‍👧‍👧 👩‍👩‍👦 👩‍👩‍👧 👩 - ‍👩‍👧‍👦 👩‍👩‍👦‍👦 👩‍👩‍👧‍👧 - 👨‍👦 👨‍👦‍👦 👨‍👧 👨‍👧‍👦 👨‍👧 - ‍👧 👩‍👦 👩‍👦‍👦\n👩‍👧 👩‍👧‍👦 - 👩‍👧‍👧 🐕‍🦺 🐈‍⬛ 🐻‍❄️ 🏳️‍🌈 - 🏳️‍⚧️ 🏴‍☠️ -STATUS:CONFIRMED -DTSTART;VALUE=DATE:20200218 -DTEND;VALUE=DATE:20200219 -END:VEVENT -BEGIN:VEVENT -CREATED:20200221T214249 -DTSTAMP:20200221T214249 -LAST-MODIFIED:20200221T214249 -UID:7MSPGE5T0S25ANPMN8Y337 -SUMMARY:UTF-8 Demo ©Markus Kuhn -CLASS:PUBLIC -DESCRIPTION:UTF-8 encoded sample plain-text file\n‾‾‾‾‾‾‾‾ - ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - ‾‾‾‾\n\nMarkus Kuhn [ˈmaʳkʊs kuːn] — 2002-07-25 CC BY\n\n\nThe ASCII compatible UTF-8 encoding used - in this plain-text file\nis defined in Unicode\, ISO 10646-1\, and RFC 227 - 9.\n\n\nUsing Unicode/UTF-8\, you can write in emails and source code thin - gs such as\n\nMathematics and sciences:\n\n ∮ E⋅da = Q\, n → ∞\, - ∑ f(i) = ∏ g(i)\, ⎧⎡⎛┌─────┐⎞⎤⎫\n - ⎪⎢⎜│a²+b³ ⎟⎥⎪\n - ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋\, α ∧ ¬β = ¬(¬α ∨ β)\, - ⎪⎢⎜│───── ⎟⎥⎪\n - ⎪⎢⎜⎷ c₈ ⎟⎥⎪\n ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ - ⊂ ℝ ⊂ ℂ\, ⎨⎢⎜ ⎟⎥⎬\n - ⎪⎢⎜ ∞ ⎟⎥⎪\n ⊥ < a - ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (⟦A⟧ ⇔ ⟪B⟫)\, ⎪⎢⎜ ⎲ - ⎟⎥⎪\n ⎪⎢⎜ ⎳a - ⁱ-bⁱ⎟⎥⎪\n 2H₂ + O₂ ⇌ 2H₂O\, R = 4.7 kΩ\, ⌀ 200 mm - ⎩⎣⎝i=1 ⎠⎦⎭\n\nLinguistics and dictionaries:\n\n ði ınt - əˈnæʃənəl fəˈnɛtık əsoʊsiˈeıʃn\n Y [ˈʏpsilɔn]\, Yen [j - ɛn]\, Yoga [ˈjoːgɑ]\n\nAPL:\n\n ((V⍳V)=⍳⍴V)/V←\,V ⌷← - ⍳→⍴∆∇⊃‾⍎⍕⌈\n\nNicer typography in plain text files:\n\ - n ╔══════════════════════ - ════════════════════╗\n ║ - ║\n ║ • ‘single’ and - “double” quotes ║\n ║ - ║\n ║ • Curly apostrophes: “We’ve been here” ║\n - ║ ║\n ║ • Latin-1 apos - trophe and accents: '´` ║\n ║ - ║\n ║ • ‚deutsche‘ „Anführungszeichen“ ║\n - ║ ║\n ║ • †\, ‡\, - ‰\, •\, 3–4\, —\, −5/+5\, ™\, … ║\n ║ - ║\n ║ • ASCII safety test: 1lI|\, 0O - D\, 8B ║\n ║ ╭───────── - ╮ ║\n ║ • the euro symbol: │ 14.95 € │ - ║\n ║ ╰─────────╯ - ║\n ╚═════════════════════ - ═════════════════════╝\n\nComb - ining characters:\n\n STARGΛ̊TE SG-1\, a = v̇ = r̈\, a⃑ ⊥ b⃑\n\ - nGreek (in Polytonic):\n\n The Greek anthem:\n\n Σὲ γνωρίζω - ἀπὸ τὴν κόψη\n τοῦ σπαθιοῦ τὴν τρομερ - ή\,\n σὲ γνωρίζω ἀπὸ τὴν ὄψη\n ποὺ μὲ - βία μετράει τὴ γῆ.\n\n ᾿Απ᾿ τὰ κόκκαλα - βγαλμένη\n τῶν ῾Ελλήνων τὰ ἱερά\n κα - ὶ σὰν πρῶτα ἀνδρειωμένη\n χαῖρε\, ὦ χα - ῖρε\, ᾿Ελευθεριά!\n\n From a speech of Demosthenes in the - 4th century BC:\n\n Οὐχὶ ταὐτὰ παρίσταταί μο - ι γιγνώσκειν\, ὦ ἄνδρες ᾿Αθηναῖοι\,\n ὅ - ταν τ᾿ εἰς τὰ πράγματα ἀποβλέψω καὶ - ὅταν πρὸς τοὺς\n λόγους οὓς ἀκούω· το - ὺς μὲν γὰρ λόγους περὶ τοῦ\n τιμωρήσα - σθαι Φίλιππον ὁρῶ γιγνομένους\, τὰ δὲ - πράγματ᾿\n εἰς τοῦτο προήκοντα\, ὥσθ - ᾿ ὅπως μὴ πεισόμεθ᾿ αὐτοὶ\n πρότερον - κακῶς σκέψασθαι δέον. οὐδέν οὖν ἄλλο - μοι δοκοῦσιν\n οἱ τὰ τοιαῦτα λέγοντες - ἢ τὴν ὑπόθεσιν\, περὶ ἧς βουλεύεσθαι\ - ,\n οὐχὶ τὴν οὖσαν παριστάντες ὑμῖν ἁ - μαρτάνειν. ἐγὼ δέ\, ὅτι μέν\n ποτ᾿ ἐξ - ῆν τῇ πόλει καὶ τὰ αὑτῆς ἔχειν ἀσφαλ - ῶς καὶ Φίλιππον\n τιμωρήσασθαι\, καὶ μ - άλ᾿ ἀκριβῶς οἶδα· ἐπ᾿ ἐμοῦ γάρ\, οὐ - πάλαι\n γέγονεν ταῦτ᾿ ἀμφότερα· νῦν - μέντοι πέπεισμαι τοῦθ᾿ ἱκανὸν\n προλ - αβεῖν ἡμῖν εἶναι τὴν πρώτην\, ὅπως το - ὺς συμμάχους\n σώσομεν. ἐὰν γὰρ τοῦτο - βεβαίως ὑπάρξῃ\, τότε καὶ περὶ τοῦ\n - τίνα τιμωρήσεταί τις καὶ ὃν τρόπον ἐ - ξέσται σκοπεῖν· πρὶν δὲ\n τὴν ἀρχὴν - ὀρθῶς ὑποθέσθαι\, μάταιον ἡγοῦμαι πε - ρὶ τῆς\n τελευτῆς ὁντινοῦν ποιεῖσθαι - λόγον.\n\n Δημοσθένους\, Γ´ ᾿Ολυνθιακὸς - \n\nGeorgian:\n\n From a Unicode conference invitation:\n\n გთხო - ვთ ახლავე გაიაროთ რეგისტრა - ცია Unicode-ის მეათე საერთაშორის - ო\n კონფერენციაზე დასასწრებ - ად\, რომელიც გაიმართება 10-12 მა - რტს\,\n ქ. მაინცში\, გერმანიაში - . კონფერენცია შეჰკრებს ერთა - დ მსოფლიოს\n ექსპერტებს ისე - თ დარგებში როგორიცაა ინტერ - ნეტი და Unicode-ი\,\n ინტერნაციონა - ლიზაცია და ლოკალიზაცია\, Unicode- - ის გამოყენება\n ოპერაციულ ს - ისტემებსა\, და გამოყენებით პ - როგრამებში\, შრიფტებში\,\n ტე - ქსტების დამუშავებასა და მრ - ავალენოვან კომპიუტერულ სის - ტემებში.\n\nRussian:\n\n From a Unicode conference invitati - on:\n\n Зарегистрируйтесь сейчас на Десяту - ю Международную Конференцию по\n Unicode\, к - оторая состоится 10-12 марта 1997 года в Майн - це в Германии.\n Конференция соберет шир - окий круг экспертов по вопросам глобаль - ного\n Интернета и Unicode\, локализации и ин - тернационализации\, воплощению и\n приме - нению Unicode в различных операционных сист - емах и программных\n приложениях\, шрифт - ах\, верстке и многоязычных компьютерных - системах.\n\nThai (UCS Level 2):\n\n Excerpt from a poetry on Th - e Romance of The Three Kingdoms (a Chinese\n classic 'San Gua'):\n\n [-- - --------------------------|------------------------]\n ๏ แผ่น - ดินฮั่นเสื่อมโทรมแสนสังเ - วช พระปกเกศกองบู๊กู้ขึ้นใ - หม่\n สิบสองกษัตริย์ก่อนหน - ้าแลถัดไป สององค์ไซร้โง - ่เขลาเบาปัญญา\n ทรงนับถือ - ขันทีเป็นที่พึ่ง บ้านเ - มืองจึงวิปริตเป็นนักหนา\n - โฮจิ๋นเรียกทัพทั่วหัวเมื - องมา หมายจะฆ่ามดชั่วตั - วสำคัญ\n เหมือนขับไสไล่เส - ือจากเคหา รับหมาป่าเข้า - มาเลยอาสัญ\n ฝ่ายอ้องอุ้นย - ุแยกให้แตกกัน ใช้สาวนั - ้นเป็นชนวนชื่นชวนใจ\n พลั - นลิฉุยกุยกีกลับก่อเหตุ - ช่างอาเพศจริงหนาฟ้าร้องไ - ห้\n ต้องรบราฆ่าฟันจนบรรลั - ย ฤๅหาใครค้ำชูกู้บรรลั - งก์ ฯ\n\n (The above is a two-column text. If combining character - s are handled\n correctly\, the lines of the second column should be alig - ned with the\n | character above.)\n\nEthiopian:\n\n Proverbs in the Amh - aric language:\n\n ሰማይ አይታረስ ንጉሥ አይከሰስ። - \n ብላ ካለኝ እንደአባቴ በቆመጠኝ።\n ጌጥ ያ - ለቤቱ ቁምጥና ነው።\n ደሀ በሕልሙ ቅቤ ባይጠ - ጣ ንጣት በገደለው።\n የአፍ ወለምታ በቅቤ አ - ይታሽም።\n አይጥ በበላ ዳዋ ተመታ።\n ሲተረጉ - ሙ ይደረግሙ።\n ቀስ በቀስ፥ ዕንቁላል በእግሩ - ይሄዳል።\n ድር ቢያብር አንበሳ ያስር።\n ሰ - ው እንደቤቱ እንጅ እንደ ጉረቤቱ አይተዳደርም - ።\n እግዜር የከፈተውን ጉሮሮ ሳይዘጋው አይድ - ርም።\n የጎረቤት ሌባ፥ ቢያዩት ይስቅ ባያዩት - ያጠልቅ።\n ሥራ ከመፍታት ልጄን ላፋታት።\n - ዓባይ ማደሪያ የለው፥ ግንድ ይዞ ይዞራል።\n - የእስላም አገሩ መካ የአሞራ አገሩ ዋርካ።\n - ተንጋሎ ቢተፉ ተመልሶ ባፉ።\n ወዳጅህ ማር ቢ - ሆን ጨርስህ አትላሰው።\n እግርህን በፍራሽህ - ልክ ዘርጋ።\n\nRunes:\n\n ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ - ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞ - ᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ\n\n (Old English\, which transcribed - into Latin reads 'He cwaeth that he\n bude thaem lande northweardum with - tha Westsae.' and means 'He said\n that he lived in the northern land ne - ar the Western Sea.')\n\nBraille:\n\n ⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇ - ⠑⠹⠰⠎ ⡣⠕⠌\n\n ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠙⠑⠁⠙⠒ ⠞ - ⠕ ⠃⠑⠛⠔ ⠺⠊⠹⠲ ⡹⠻⠑ ⠊⠎ ⠝⠕ ⠙⠳⠃⠞\n ⠱ - ⠁⠞⠑⠧⠻ ⠁⠃⠳⠞ ⠹⠁⠞⠲ ⡹⠑ ⠗⠑⠛⠊⠌⠻ ⠕⠋ - ⠙⠊⠎ ⠃⠥⠗⠊⠁⠇ ⠺⠁⠎\n ⠎⠊⠛⠝⠫ ⠃⠹ ⠹⠑ - ⠊⠇⠻⠛⠹⠍⠁⠝⠂ ⠹⠑ ⠊⠇⠻⠅⠂ ⠹⠑ ⠥⠝⠙⠻⠞ - ⠁⠅⠻⠂\n ⠁⠝⠙ ⠹⠑ ⠡⠊⠑⠋ ⠍⠳⠗⠝⠻⠲ ⡎⠊⠗ - ⠕⠕⠛⠑ ⠎⠊⠛⠝⠫ ⠊⠞⠲ ⡁⠝⠙\n ⡎⠊⠗⠕⠕⠛⠑ - ⠰⠎ ⠝⠁⠍⠑ ⠺⠁⠎ ⠛⠕⠕⠙ ⠥⠏⠕⠝ ⠰⡡⠁⠝⠛⠑ - ⠂ ⠋⠕⠗ ⠁⠝⠹⠹⠔⠛ ⠙⠑\n ⠡⠕⠎⠑ ⠞⠕ ⠏⠥⠞ - ⠙⠊⠎ ⠙⠁⠝⠙ ⠞⠕⠲\n\n ⡕⠇⠙ ⡍⠜⠇⠑⠹ ⠺⠁⠎ - ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲\n\n ⡍⠔ - ⠙⠖ ⡊ ⠙⠕⠝⠰⠞ ⠍⠑⠁⠝ ⠞⠕ ⠎⠁⠹ ⠹⠁⠞ ⡊ ⠅ - ⠝⠪⠂ ⠕⠋ ⠍⠹\n ⠪⠝ ⠅⠝⠪⠇⠫⠛⠑⠂ ⠱⠁⠞ ⠹ - ⠻⠑ ⠊⠎ ⠏⠜⠞⠊⠊⠥⠇⠜⠇⠹ ⠙⠑⠁⠙ ⠁⠃⠳⠞\n - ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ ⡊ ⠍⠊⠣⠞ ⠙⠁⠧⠑ ⠃⠑⠲ - ⠔⠊⠇⠔⠫⠂ ⠍⠹⠎⠑⠇⠋⠂ ⠞⠕\n ⠗⠑⠛⠜⠙ ⠁ - ⠊⠕⠋⠋⠔⠤⠝⠁⠊⠇ ⠁⠎ ⠹⠑ ⠙⠑⠁⠙⠑⠌ ⠏⠊⠑ - ⠊⠑ ⠕⠋ ⠊⠗⠕⠝⠍⠕⠝⠛⠻⠹\n ⠔ ⠹⠑ ⠞⠗⠁⠙⠑ - ⠲ ⡃⠥⠞ ⠹⠑ ⠺⠊⠎⠙⠕⠍ ⠕⠋ ⠳⠗ ⠁⠝⠊⠑⠌⠕ - ⠗⠎\n ⠊⠎ ⠔ ⠹⠑ ⠎⠊⠍⠊⠇⠑⠆ ⠁⠝⠙ ⠍⠹ ⠥⠝ - ⠙⠁⠇⠇⠪⠫ ⠙⠁⠝⠙⠎\n ⠩⠁⠇⠇ ⠝⠕⠞ ⠙⠊⠌⠥ - ⠗⠃ ⠊⠞⠂ ⠕⠗ ⠹⠑ ⡊⠳⠝⠞⠗⠹⠰⠎ ⠙⠕⠝⠑ ⠋ - ⠕⠗⠲ ⡹⠳\n ⠺⠊⠇⠇ ⠹⠻⠑⠋⠕⠗⠑ ⠏⠻⠍⠊⠞ ⠍ - ⠑ ⠞⠕ ⠗⠑⠏⠑⠁⠞⠂ ⠑⠍⠏⠙⠁⠞⠊⠊⠁⠇⠇⠹⠂ - ⠹⠁⠞\n ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙ - ⠕⠕⠗⠤⠝⠁⠊⠇⠲\n\n (The first couple of paragraphs of "A Chr - istmas Carol" by Dickens)\n\nCompact font selection example text:\n\n ABC - DEFGHIJKLMNOPQRSTUVWXYZ /0123456789\n abcdefghijklmnopqrstuvwxyz £©µÀ - ÆÖÞßéöÿ\n –—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩ - αβγδω АБВГДабвгд\n ∀∂∈ℝ∧∪≡∞ ↑↗↨↻ - ⇣ ┐┼╔╘░►☺♀ fi�⑀₂ἠḂӥẄɐː⍎אԱა\n\nGree - tings in various languages:\n\n Hello world\, Καλημέρα κόσμ - ε\, コンニチハ\n\nBox drawing alignment tests: - █\n - ▉\n ╔══╦══╗ ┌──┬──┐ - ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎ - ┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳\n - ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │ - ╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋ - ╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳\n ║│╲ ╱│║ - │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ - ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳ - \n ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─ - ╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏ - ┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳\n ║│╱ ╲│║ │║ - ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒ - ▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎\n ║└─╥─┘║ - │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃ - └─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏ - \n ╚══╩══╝ └──┴──┘ ╰──┴──╯ - ╰──┴──╯ ┗━━┻━━┛ ▗▄▖▛▀▜ └╌ - ╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█\n - ▝▀▘▙▄▟ -STATUS:CONFIRMED -DTSTART;TZID=Europe/Berlin:20200217T100000 -DTEND;TZID=Europe/Berlin:20200217T130000 -END:VEVENT -BEGIN:VEVENT -CREATED:20200221T214509 -DTSTAMP:20200221T214509 -LAST-MODIFIED:20200221T214509 -UID:Q61XC6MORCNB0Y1OH7G2B -SUMMARY:Emoji Keyboard/Display Test Data v1️⃣2️⃣ ©️Unicode -CLASS:PUBLIC -DESCRIPTION:# emoji-test.txt\n# Date: 2019-10-12\, 00:43:03 GMT\n# © 2019 - Unicode®\, Inc.\n# Unicode and the Unicode Logo are registered trademarks - of Unicode\, Inc. in the U.S. and other countries.\n# For terms of use\, - see http://www.unicode.org/terms_of_use.html\n#\n# Emoji Keyboard/Display - Test Data for UTS #51\n# Version: 12.1\n#\n# For documentation and usage\, - see http://www.unicode.org/reports/tr51\n#\n# This file provides data for - testing which emoji forms should be in keyboards and which should also be - displayed/processed.\n# Format: code points\; status # emoji name\n# - Code points — list of one or more hex code points\, separated by spaces\ - n# Status\n# component — an Emoji_Component\,\n# - excluding Regional_Indicators\, ASCII\, and non-E - moji.\n# fully-qualified — a fully-qualified emoji (see ED-18 - in UTS #51)\,\n# excluding Emoji_Component\n# - minimally-qualified — a minimally-qualified emoji (see ED-18a in U - TS #51)\n# unqualified — a unqualified emoji (See ED-19 in - UTS #51)\n# Notes:\n# • This includes the emoji components that need - emoji presentation (skin tone and hair)\n# when isolated\, but omits t - he components that need not have an emoji\n# presentation when isolate - d.\n# • The RGI set is covered by the listed fully-qualified emoji. \n - # • The listed minimally-qualified and unqualified cover all cases whe - re an\n# element of the RGI set is missing one or more emoji presentat - ion selectors.\n# • The file is in CLDR order\, not codepoint order. T - his is recommended (but not required!) for keyboard palettes.\n# • The - groups and subgroups are illustrative. See the Emoji Order chart for more - information.\n\n\n# group: Smileys & Emotion\n\n# subgroup: face-smiling\ - n1F600 \; fully-qualified # 😀 - E2.0 grinning face\n1F603 \; fully-qu - alified # 😃 E2.0 grinning face with big eyes\n1F604 - \; fully-qualified # 😄 E2.0 grinning face wit - h smiling eyes\n1F601 \; fully-qualif - ied # 😁 E2.0 beaming face with smiling eyes\n1F606 - \; fully-qualified # 😆 E2.0 grinning squinting - face\n1F605 \; fully-qualified # - 😅 E2.0 grinning face with sweat\n1F923 - \; fully-qualified # 🤣 E4.0 rolling on the floor laughing\n1F - 602 \; fully-qualified # 😂 E2. - 0 face with tears of joy\n1F642 \; fu - lly-qualified # 🙂 E2.0 slightly smiling face\n1F643 - \; fully-qualified # 🙃 E2.0 upside-down face\ - n1F609 \; fully-qualified # 😉 - E2.0 winking face\n1F60A \; fully-qua - lified # 😊 E2.0 smiling face with smiling eyes\n1F607 - \; fully-qualified # 😇 E2.0 smiling face wi - th halo\n\n# subgroup: face-affection\n1F970 - \; fully-qualified # 🥰 E11.0 smiling face with hearts\n1F60 - D \; fully-qualified # 😍 E2.0 - smiling face with heart-eyes\n1F929 \ - ; fully-qualified # 🤩 E5.0 star-struck\n1F618 - \; fully-qualified # 😘 E2.0 face blowing a kiss\n1F - 617 \; fully-qualified # 😗 E2. - 0 kissing face\n263A FE0F \; fully-qualif - ied # ☺️ E2.0 smiling face\n263A - \; unqualified # ☺ E2.0 smiling face\n1F61A - \; fully-qualified # 😚 E2.0 kissing face with - closed eyes\n1F619 \; fully-qualifie - d # 😙 E2.0 kissing face with smiling eyes\n\n# subgroup: face-tongu - e\n1F60B \; fully-qualified # - 😋 E2.0 face savoring food\n1F61B \ - ; fully-qualified # 😛 E2.0 face with tongue\n1F61C - \; fully-qualified # 😜 E2.0 winking face with - tongue\n1F92A \; fully-qualified - # 🤪 E5.0 zany face\n1F61D \; fully - -qualified # 😝 E2.0 squinting face with tongue\n1F911 - \; fully-qualified # 🤑 E2.0 money-mouth fac - e\n\n# subgroup: face-hand\n1F917 \; - fully-qualified # 🤗 E2.0 hugging face\n1F92D - \; fully-qualified # 🤭 E5.0 face with hand over mout - h\n1F92B \; fully-qualified # - 🤫 E5.0 shushing face\n1F914 \; ful - ly-qualified # 🤔 E2.0 thinking face\n\n# subgroup: face-neutral-ske - ptical\n1F910 \; fully-qualified - # 🤐 E2.0 zipper-mouth face\n1F928 - \; fully-qualified # 🤨 E5.0 face with raised eyebrow\n1F610 - \; fully-qualified # 😐 E2.0 neutral f - ace\n1F611 \; fully-qualified # - 😑 E2.0 expressionless face\n1F636 - \; fully-qualified # 😶 E2.0 face without mouth\n1F60F - \; fully-qualified # 😏 E2.0 smirking face\n - 1F612 \; fully-qualified # 😒 E - 2.0 unamused face\n1F644 \; fully-qua - lified # 🙄 E2.0 face with rolling eyes\n1F62C - \; fully-qualified # 😬 E2.0 grimacing face\n1F925 - \; fully-qualified # 🤥 E4.0 lyi - ng face\n\n# subgroup: face-sleepy\n1F60C - \; fully-qualified # 😌 E2.0 relieved face\n1F614 - \; fully-qualified # 😔 E2.0 pensive face\n1 - F62A \; fully-qualified # 😪 E2 - .0 sleepy face\n1F924 \; fully-qualif - ied # 🤤 E4.0 drooling face\n1F634 - \; fully-qualified # 😴 E2.0 sleeping face\n\n# subgroup: face-u - nwell\n1F637 \; fully-qualified # - 😷 E2.0 face with medical mask\n1F912 - \; fully-qualified # 🤒 E2.0 face with thermometer\n1F915 - \; fully-qualified # 🤕 E2.0 face wit - h head-bandage\n1F922 \; fully-qualif - ied # 🤢 E4.0 nauseated face\n1F92E - \; fully-qualified # 🤮 E5.0 face vomiting\n1F927 - \; fully-qualified # 🤧 E4.0 sneezing face\n - 1F975 \; fully-qualified # 🥵 E - 11.0 hot face\n1F976 \; fully-qualifi - ed # 🥶 E11.0 cold face\n1F974 - \; fully-qualified # 🥴 E11.0 woozy face\n1F635 - \; fully-qualified # 😵 E2.0 dizzy face\n1F92F - \; fully-qualified # 🤯 E5.0 explod - ing head\n\n# subgroup: face-hat\n1F920 - \; fully-qualified # 🤠 E4.0 cowboy hat face\n1F973 - \; fully-qualified # 🥳 E11.0 partying face\ - n\n# subgroup: face-glasses\n1F60E \; - fully-qualified # 😎 E2.0 smiling face with sunglasses\n1F913 - \; fully-qualified # 🤓 E2.0 nerd fa - ce\n1F9D0 \; fully-qualified # - 🧐 E5.0 face with monocle\n\n# subgroup: face-concerned\n1F615 - \; fully-qualified # 😕 E2.0 confused fa - ce\n1F61F \; fully-qualified # - 😟 E2.0 worried face\n1F641 \; full - y-qualified # 🙁 E2.0 slightly frowning face\n2639 FE0F - \; fully-qualified # ☹️ E2.0 frowning face\n2 - 639 \; unqualified # ☹ E2. - 0 frowning face\n1F62E \; fully-quali - fied # 😮 E2.0 face with open mouth\n1F62F - \; fully-qualified # 😯 E2.0 hushed face\n1F632 - \; fully-qualified # 😲 E2.0 astonished - face\n1F633 \; fully-qualified # - 😳 E2.0 flushed face\n1F97A \; ful - ly-qualified # 🥺 E11.0 pleading face\n1F626 - \; fully-qualified # 😦 E2.0 frowning face with open m - outh\n1F627 \; fully-qualified # - 😧 E2.0 anguished face\n1F628 \; fu - lly-qualified # 😨 E2.0 fearful face\n1F630 - \; fully-qualified # 😰 E2.0 anxious face with sweat\n1 - F625 \; fully-qualified # 😥 E2 - .0 sad but relieved face\n1F622 \; fu - lly-qualified # 😢 E2.0 crying face\n1F62D - \; fully-qualified # 😭 E2.0 loudly crying face\n1F631 - \; fully-qualified # 😱 E2.0 fac - e screaming in fear\n1F616 \; fully-q - ualified # 😖 E2.0 confounded face\n1F623 - \; fully-qualified # �� E2.0 persevering face\n1F61E - \; fully-qualified # 😞 E2.0 disa - ppointed face\n1F613 \; fully-qualifi - ed # 😓 E2.0 downcast face with sweat\n1F629 - \; fully-qualified # 😩 E2.0 weary face\n1F62B - \; fully-qualified # 😫 E2.0 tired fac - e\n1F971 \; fully-qualified # - 🥱 E12.1 yawning face\n\n# subgroup: face-negative\n1F624 - \; fully-qualified # 😤 E2.0 face with steam - from nose\n1F621 \; fully-qualified - # 😡 E2.0 pouting face\n1F620 \; - fully-qualified # 😠 E2.0 angry face\n1F92C - \; fully-qualified # 🤬 E5.0 face with symbols on mout - h\n1F608 \; fully-qualified # - 😈 E2.0 smiling face with horns\n1F47F - \; fully-qualified # 👿 E2.0 angry face with horns\n1F480 - \; fully-qualified # 💀 E2.0 skull\n2 - 620 FE0F \; fully-qualified # ☠️ - E2.0 skull and crossbones\n2620 \; u - nqualified # ☠ E2.0 skull and crossbones\n\n# subgroup: face-cos - tume\n1F4A9 \; fully-qualified # - 💩 E2.0 pile of poo\n1F921 \; fully - -qualified # 🤡 E4.0 clown face\n1F479 - \; fully-qualified # 👹 E2.0 ogre\n1F47A - \; fully-qualified # 👺 E2.0 goblin\n1F47B - \; fully-qualified # 👻 E2.0 ghost\n1F - 47D \; fully-qualified # 👽 E2. - 0 alien\n1F47E \; fully-qualified - # 👾 E2.0 alien monster\n1F916 \; - fully-qualified # 🤖 E2.0 robot\n\n# subgroup: cat-face\n1F63A - \; fully-qualified # 😺 E2.0 grinnin - g cat\n1F638 \; fully-qualified # - 😸 E2.0 grinning cat with smiling eyes\n1F639 - \; fully-qualified # 😹 E2.0 cat with tears of joy\n1F63 - B \; fully-qualified # 😻 E2.0 - smiling cat with heart-eyes\n1F63C \; - fully-qualified # 😼 E2.0 cat with wry smile\n1F63D - \; fully-qualified # 😽 E2.0 kissing cat\n1F64 - 0 \; fully-qualified # 🙀 E2.0 - weary cat\n1F63F \; fully-qualified - # 😿 E2.0 crying cat\n1F63E \; f - ully-qualified # 😾 E2.0 pouting cat\n\n# subgroup: monkey-face\n1F6 - 48 \; fully-qualified # 🙈 E2.0 - see-no-evil monkey\n1F649 \; fully-q - ualified # 🙉 E2.0 hear-no-evil monkey\n1F64A - \; fully-qualified # 🙊 E2.0 speak-no-evil monkey\n\n - # subgroup: emotion\n1F48B \; fully-q - ualified # 💋 E2.0 kiss mark\n1F48C - \; fully-qualified # 💌 E2.0 love letter\n1F498 - \; fully-qualified # 💘 E2.0 heart with arrow\ - n1F49D \; fully-qualified # 💝 - E2.0 heart with ribbon\n1F496 \; full - y-qualified # 💖 E2.0 sparkling heart\n1F497 - \; fully-qualified # 💗 E2.0 growing heart\n1F493 - \; fully-qualified # 💓 E2.0 beatin - g heart\n1F49E \; fully-qualified - # 💞 E2.0 revolving hearts\n1F495 - \; fully-qualified # 💕 E2.0 two hearts\n1F49F - \; fully-qualified # 💟 E2.0 heart decoration\n2763 - FE0F \; fully-qualified # ❣️ E2.0 - heart exclamation\n2763 \; unqualif - ied # ❣ E2.0 heart exclamation\n1F494 - \; fully-qualified # 💔 E2.0 broken heart\n2764 FE0F - \; fully-qualified # ❤️ E2.0 red hear - t\n2764 \; unqualified # ❤ - E2.0 red heart\n1F9E1 \; fully-quali - fied # 🧡 E5.0 orange heart\n1F49B - \; fully-qualified # 💛 E2.0 yellow heart\n1F49A - \; fully-qualified # 💚 E2.0 green heart\n1F49 - 9 \; fully-qualified # 💙 E2.0 - blue heart\n1F49C \; fully-qualified - # 💜 E2.0 purple heart\n1F90E \ - ; fully-qualified # 🤎 E12.1 brown heart\n1F5A4 - \; fully-qualified # 🖤 E4.0 black heart\n1F90D - \; fully-qualified # 🤍 E12.1 whit - e heart\n1F4AF \; fully-qualified - # 💯 E2.0 hundred points\n1F4A2 \; - fully-qualified # 💢 E2.0 anger symbol\n1F4A5 - \; fully-qualified # 💥 E2.0 collision\n1F4AB - \; fully-qualified # 💫 E2.0 dizzy\n1 - F4A6 \; fully-qualified # 💦 E2 - .0 sweat droplets\n1F4A8 \; fully-qua - lified # 💨 E2.0 dashing away\n1F573 FE0F - \; fully-qualified # 🕳️ E2.0 hole\n1F573 - \; unqualified # 🕳 E2.0 hole\n1F4A3 - \; fully-qualified # 💣 E2.0 bomb\n1F4A - C \; fully-qualified # 💬 E2.0 - speech balloon\n1F441 FE0F 200D 1F5E8 FE0F \; fully-qualif - ied # 👁️‍🗨️ E2.0 eye in speech bubble\n1F441 200D 1F5E8 FE - 0F \; unqualified # 👁‍🗨️ E2.0 eye i - n speech bubble\n1F441 FE0F 200D 1F5E8 \; unqualified - # 👁️‍🗨 E2.0 eye in speech bubble\n1F441 200D 1F5E8 - \; unqualified # 👁‍🗨 E2.0 eye in spe - ech bubble\n1F5E8 FE0F \; fully-qualified - # 🗨️ E2.0 left speech bubble\n1F5E8 - \; unqualified # 🗨 E2.0 left speech bubble\n1F5EF FE0F - \; fully-qualified # 🗯️ E2.0 righ - t anger bubble\n1F5EF \; unqualified - # 🗯 E2.0 right anger bubble\n1F4AD - \; fully-qualified # 💭 E2.0 thought balloon\n1F4A4 - \; fully-qualified # 💤 E2.0 zzz\n\n# - Smileys & Emotion subtotal: 160\n# Smileys & Emotion subtotal: 160 w/o m - odifiers\n\n# group: People & Body\n\n# subgroup: hand-fingers-open\n1F44B - \; fully-qualified # 👋 E2.0 w - aving hand\n1F44B 1F3FB \; fully-qualified - # 👋🏻 E2.0 waving hand: light skin tone\n1F44B 1F3FC - \; fully-qualified # 👋🏼 E2.0 waving hand: med - ium-light skin tone\n1F44B 1F3FD \; fully-q - ualified # 👋🏽 E2.0 waving hand: medium skin tone\n1F44B 1F3FE - \; fully-qualified # 👋🏾 E2.0 waving - hand: medium-dark skin tone\n1F44B 1F3FF \ - ; fully-qualified # 👋🏿 E2.0 waving hand: dark skin tone\n1F91A - \; fully-qualified # 🤚 E4.0 rai - sed back of hand\n1F91A 1F3FB \; fully-qual - ified # 🤚🏻 E4.0 raised back of hand: light skin tone\n1F91A 1F3F - C \; fully-qualified # 🤚🏼 E4.0 ra - ised back of hand: medium-light skin tone\n1F91A 1F3FD - \; fully-qualified # 🤚🏽 E4.0 raised back of hand: me - dium skin tone\n1F91A 1F3FE \; fully-qualif - ied # 🤚🏾 E4.0 raised back of hand: medium-dark skin tone\n1F91A - 1F3FF \; fully-qualified # 🤚🏿 E4. - 0 raised back of hand: dark skin tone\n1F590 FE0F - \; fully-qualified # 🖐️ E2.0 hand with fingers splayed\n1 - F590 \; unqualified # 🖐 E2 - .0 hand with fingers splayed\n1F590 1F3FB \ - ; fully-qualified # 🖐🏻 E2.0 hand with fingers splayed: light ski - n tone\n1F590 1F3FC \; fully-qualified - # 🖐🏼 E2.0 hand with fingers splayed: medium-light skin tone\n1F590 1 - F3FD \; fully-qualified # 🖐🏽 E2.0 - hand with fingers splayed: medium skin tone\n1F590 1F3FE - \; fully-qualified # 🖐🏾 E2.0 hand with fingers sp - layed: medium-dark skin tone\n1F590 1F3FF \ - ; fully-qualified # 🖐🏿 E2.0 hand with fingers splayed: dark skin - tone\n270B \; fully-qualified # - ✋ E2.0 raised hand\n270B 1F3FB \; fully - -qualified # ✋🏻 E2.0 raised hand: light skin tone\n270B 1F3FC - \; fully-qualified # ✋🏼 E2.0 raised - hand: medium-light skin tone\n270B 1F3FD \ - ; fully-qualified # ✋🏽 E2.0 raised hand: medium skin tone\n270B 1 - F3FE \; fully-qualified # ✋🏾 E2.0 - raised hand: medium-dark skin tone\n270B 1F3FF - \; fully-qualified # ✋🏿 E2.0 raised hand: dark skin tone\n1 - F596 \; fully-qualified # 🖖 E2 - .0 vulcan salute\n1F596 1F3FB \; fully-qual - ified # 🖖🏻 E2.0 vulcan salute: light skin tone\n1F596 1F3FC - \; fully-qualified # 🖖🏼 E2.0 vulcan s - alute: medium-light skin tone\n1F596 1F3FD - \; fully-qualified # 🖖🏽 E2.0 vulcan salute: medium skin tone\n1F - 596 1F3FE \; fully-qualified # 🖖🏾 - E2.0 vulcan salute: medium-dark skin tone\n1F596 1F3FF - \; fully-qualified # 🖖🏿 E2.0 vulcan salute: dark sk - in tone\n\n# subgroup: hand-fingers-partial\n1F44C - \; fully-qualified # 👌 E2.0 OK hand\n1F44C 1F3FB - \; fully-qualified # 👌🏻 E2.0 OK hand: - light skin tone\n1F44C 1F3FC \; fully-qual - ified # 👌🏼 E2.0 OK hand: medium-light skin tone\n1F44C 1F3FD - \; fully-qualified # 👌🏽 E2.0 OK hand - : medium skin tone\n1F44C 1F3FE \; fully-qu - alified # 👌🏾 E2.0 OK hand: medium-dark skin tone\n1F44C 1F3FF - \; fully-qualified # 👌🏿 E2.0 OK han - d: dark skin tone\n1F90F \; fully-qua - lified # 🤏 E12.1 pinching hand\n1F90F 1F3FB - \; fully-qualified # 🤏🏻 E12.1 pinching hand: light skin - tone\n1F90F 1F3FC \; fully-qualified # - 🤏🏼 E12.1 pinching hand: medium-light skin tone\n1F90F 1F3FD - \; fully-qualified # 🤏🏽 E12.1 pinching ha - nd: medium skin tone\n1F90F 1F3FE \; fully- - qualified # 🤏🏾 E12.1 pinching hand: medium-dark skin tone\n1F90F - 1F3FF \; fully-qualified # 🤏🏿 E1 - 2.1 pinching hand: dark skin tone\n270C FE0F - \; fully-qualified # ✌️ E2.0 victory hand\n270C - \; unqualified # ✌ E2.0 victory hand\n27 - 0C 1F3FB \; fully-qualified # ✌🏻 - E2.0 victory hand: light skin tone\n270C 1F3FC - \; fully-qualified # ✌🏼 E2.0 victory hand: medium-light skin - tone\n270C 1F3FD \; fully-qualified # - ✌🏽 E2.0 victory hand: medium skin tone\n270C 1F3FE - \; fully-qualified # ✌🏾 E2.0 victory hand: medium- - dark skin tone\n270C 1F3FF \; fully-qualif - ied # ✌🏿 E2.0 victory hand: dark skin tone\n1F91E - \; fully-qualified # 🤞 E4.0 crossed fingers\n - 1F91E 1F3FB \; fully-qualified # 🤞 - 🏻 E4.0 crossed fingers: light skin tone\n1F91E 1F3FC - \; fully-qualified # 🤞🏼 E4.0 crossed fingers: mediu - m-light skin tone\n1F91E 1F3FD \; fully-qua - lified # 🤞🏽 E4.0 crossed fingers: medium skin tone\n1F91E 1F3FE - \; fully-qualified # 🤞🏾 E4.0 cros - sed fingers: medium-dark skin tone\n1F91E 1F3FF - \; fully-qualified # 🤞🏿 E4.0 crossed fingers: dark skin ton - e\n1F91F \; fully-qualified # - 🤟 E5.0 love-you gesture\n1F91F 1F3FB \; - fully-qualified # 🤟🏻 E5.0 love-you gesture: light skin tone\n1F9 - 1F 1F3FC \; fully-qualified # 🤟🏼 - E5.0 love-you gesture: medium-light skin tone\n1F91F 1F3FD - \; fully-qualified # 🤟🏽 E5.0 love-you gesture: m - edium skin tone\n1F91F 1F3FE \; fully-quali - fied # 🤟🏾 E5.0 love-you gesture: medium-dark skin tone\n1F91F 1F - 3FF \; fully-qualified # 🤟�� E5. - 0 love-you gesture: dark skin tone\n1F918 - \; fully-qualified # 🤘 E2.0 sign of the horns\n1F918 1F3FB - \; fully-qualified # 🤘🏻 E2.0 sign of - the horns: light skin tone\n1F918 1F3FC \; - fully-qualified # 🤘🏼 E2.0 sign of the horns: medium-light skin - tone\n1F918 1F3FD \; fully-qualified # - 🤘🏽 E2.0 sign of the horns: medium skin tone\n1F918 1F3FE - \; fully-qualified # 🤘🏾 E2.0 sign of the hor - ns: medium-dark skin tone\n1F918 1F3FF \; f - ully-qualified # ��🏿 E2.0 sign of the horns: dark skin tone\n1F - 919 \; fully-qualified # 🤙 E4. - 0 call me hand\n1F919 1F3FB \; fully-qualif - ied # 🤙🏻 E4.0 call me hand: light skin tone\n1F919 1F3FC - \; fully-qualified # 🤙🏼 E4.0 call me han - d: medium-light skin tone\n1F919 1F3FD \; f - ully-qualified # 🤙🏽 E4.0 call me hand: medium skin tone\n1F919 1 - F3FE \; fully-qualified # 🤙🏾 E4.0 - call me hand: medium-dark skin tone\n1F919 1F3FF - \; fully-qualified # 🤙🏿 E4.0 call me hand: dark skin tone - \n\n# subgroup: hand-single-finger\n1F448 - \; fully-qualified # 👈 E2.0 backhand index pointing left\n1F44 - 8 1F3FB \; fully-qualified # 👈🏻 E - 2.0 backhand index pointing left: light skin tone\n1F448 1F3FC - \; fully-qualified # 👈🏼 E2.0 backhand index - pointing left: medium-light skin tone\n1F448 1F3FD - \; fully-qualified # 👈🏽 E2.0 backhand index pointing lef - t: medium skin tone\n1F448 1F3FE \; fully-q - ualified # 👈🏾 E2.0 backhand index pointing left: medium-dark ski - n tone\n1F448 1F3FF \; fully-qualified - # 👈🏿 E2.0 backhand index pointing left: dark skin tone\n1F449 - \; fully-qualified # 👉 E2.0 backhand - index pointing right\n1F449 1F3FB \; fully - -qualified # 👉🏻 E2.0 backhand index pointing right: light skin t - one\n1F449 1F3FC \; fully-qualified # - 👉🏼 E2.0 backhand index pointing right: medium-light skin tone\n1F449 - 1F3FD \; fully-qualified # 👉🏽 E2 - .0 backhand index pointing right: medium skin tone\n1F449 1F3FE - \; fully-qualified # 👉🏾 E2.0 backhand index - pointing right: medium-dark skin tone\n1F449 1F3FF - \; fully-qualified # 👉🏿 E2.0 backhand index pointing ri - ght: dark skin tone\n1F446 \; fully-q - ualified # 👆 E2.0 backhand index pointing up\n1F446 1F3FB - \; fully-qualified # 👆🏻 E2.0 backhand inde - x pointing up: light skin tone\n1F446 1F3FC - \; fully-qualified # 👆🏼 E2.0 backhand index pointing up: medium - -light skin tone\n1F446 1F3FD \; fully-qual - ified # 👆🏽 E2.0 backhand index pointing up: medium skin tone\n1F - 446 1F3FE \; fully-qualified # 👆🏾 - E2.0 backhand index pointing up: medium-dark skin tone\n1F446 1F3FF - \; fully-qualified # 👆🏿 E2.0 backhand - index pointing up: dark skin tone\n1F595 - \; fully-qualified # 🖕 E2.0 middle finger\n1F595 1F3FB - \; fully-qualified # 🖕🏻 E2.0 middle finge - r: light skin tone\n1F595 1F3FC \; fully-qu - alified # 🖕🏼 E2.0 middle finger: medium-light skin tone\n1F595 1 - F3FD \; fully-qualified # 🖕🏽 E2.0 - middle finger: medium skin tone\n1F595 1F3FE - \; fully-qualified # ��🏾 E2.0 middle finger: medium-dark ski - n tone\n1F595 1F3FF \; fully-qualified - # 🖕🏿 E2.0 middle finger: dark skin tone\n1F447 - \; fully-qualified # �� E2.0 backhand index pointi - ng down\n1F447 1F3FB \; fully-qualified - # 👇🏻 E2.0 backhand index pointing down: light skin tone\n1F447 1F3F - C \; fully-qualified # 👇🏼 E2.0 ba - ckhand index pointing down: medium-light skin tone\n1F447 1F3FD - \; fully-qualified # 👇🏽 E2.0 backhand index - pointing down: medium skin tone\n1F447 1F3FE - \; fully-qualified # 👇🏾 E2.0 backhand index pointing down: me - dium-dark skin tone\n1F447 1F3FF \; fully-q - ualified # 👇🏿 E2.0 backhand index pointing down: dark skin tone\ - n261D FE0F \; fully-qualified # ☝ - ️ E2.0 index pointing up\n261D \; - unqualified # ☝ E2.0 index pointing up\n261D 1F3FB - \; fully-qualified # ☝🏻 E2.0 index pointing up - : light skin tone\n261D 1F3FC \; fully-qua - lified # ☝🏼 E2.0 index pointing up: medium-light skin tone\n261D - 1F3FD \; fully-qualified # ☝🏽 E2. - 0 index pointing up: medium skin tone\n261D 1F3FE - \; fully-qualified # ☝🏾 E2.0 index pointing up: medium-da - rk skin tone\n261D 1F3FF \; fully-qualifie - d # ☝🏿 E2.0 index pointing up: dark skin tone\n\n# subgroup: hand - -fingers-closed\n1F44D \; fully-quali - fied # 👍 E2.0 thumbs up\n1F44D 1F3FB - \; fully-qualified # 👍🏻 E2.0 thumbs up: light skin tone\n1F44D - 1F3FC \; fully-qualified # 👍🏼 E2. - 0 thumbs up: medium-light skin tone\n1F44D 1F3FD - \; fully-qualified # 👍🏽 E2.0 thumbs up: medium skin tone\n - 1F44D 1F3FE \; fully-qualified # 👍 - 🏾 E2.0 thumbs up: medium-dark skin tone\n1F44D 1F3FF - \; fully-qualified # 👍🏿 E2.0 thumbs up: dark skin t - one\n1F44E \; fully-qualified # - 👎 E2.0 thumbs down\n1F44E 1F3FB \; fully - -qualified # 👎🏻 E2.0 thumbs down: light skin tone\n1F44E 1F3FC - \; fully-qualified # 👎🏼 E2.0 thumb - s down: medium-light skin tone\n1F44E 1F3FD - \; fully-qualified # 👎🏽 E2.0 thumbs down: medium skin tone\n1F4 - 4E 1F3FE \; fully-qualified # 👎🏾 - E2.0 thumbs down: medium-dark skin tone\n1F44E 1F3FF - \; fully-qualified # 👎🏿 E2.0 thumbs down: dark skin to - ne\n270A \; fully-qualified # - ✊ E2.0 raised fist\n270A 1F3FB \; fully- - qualified # ✊🏻 E2.0 raised fist: light skin tone\n270A 1F3FC - \; fully-qualified # ✊🏼 E2.0 raised f - ist: medium-light skin tone\n270A 1F3FD \; - fully-qualified # ✊🏽 E2.0 raised fist: medium skin tone\n270A 1F - 3FE \; fully-qualified # ✊🏾 E2.0 - raised fist: medium-dark skin tone\n270A 1F3FF - \; fully-qualified # ✊🏿 E2.0 raised fist: dark skin tone\n1F - 44A \; fully-qualified # 👊 E2. - 0 oncoming fist\n1F44A 1F3FB \; fully-quali - fied # 👊🏻 E2.0 oncoming fist: light skin tone\n1F44A 1F3FC - \; fully-qualified # 👊🏼 E2.0 oncoming - fist: medium-light skin tone\n1F44A 1F3FD \ - ; fully-qualified # 👊🏽 E2.0 oncoming fist: medium skin tone\n1F4 - 4A 1F3FE \; fully-qualified # 👊🏾 - E2.0 oncoming fist: medium-dark skin tone\n1F44A 1F3FF - \; fully-qualified # 👊🏿 E2.0 oncoming fist: dark ski - n tone\n1F91B \; fully-qualified - # 🤛 E4.0 left-facing fist\n1F91B 1F3FB \ - ; fully-qualified # 🤛🏻 E4.0 left-facing fist: light skin tone\n1 - F91B 1F3FC \; fully-qualified # 🤛 - 🏼 E4.0 left-facing fist: medium-light skin tone\n1F91B 1F3FD - \; fully-qualified # 🤛🏽 E4.0 left-facing fi - st: medium skin tone\n1F91B 1F3FE \; fully- - qualified # 🤛🏾 E4.0 left-facing fist: medium-dark skin tone\n1F9 - 1B 1F3FF \; fully-qualified # 🤛🏿 - E4.0 left-facing fist: dark skin tone\n1F91C - \; fully-qualified # 🤜 E4.0 right-facing fist\n1F91C 1F3FB - \; fully-qualified # ��🏻 E4.0 ri - ght-facing fist: light skin tone\n1F91C 1F3FC - \; fully-qualified # 🤜🏼 E4.0 right-facing fist: medium-light - skin tone\n1F91C 1F3FD \; fully-qualified - # 🤜🏽 E4.0 right-facing fist: medium skin tone\n1F91C 1F3FE - \; fully-qualified # 🤜🏾 E4.0 right-faci - ng fist: medium-dark skin tone\n1F91C 1F3FF - \; fully-qualified # 🤜🏿 E4.0 right-facing fist: dark skin tone\ - n\n# subgroup: hands\n1F44F \; fully- - qualified # 👏 E2.0 clapping hands\n1F44F 1F3FB - \; fully-qualified # 👏🏻 E2.0 clapping hands: light sk - in tone\n1F44F 1F3FC \; fully-qualified - # 👏🏼 E2.0 clapping hands: medium-light skin tone\n1F44F 1F3FD - \; fully-qualified # 👏🏽 E2.0 clapping - hands: medium skin tone\n1F44F 1F3FE \; ful - ly-qualified # 👏🏾 E2.0 clapping hands: medium-dark skin tone\n1F - 44F 1F3FF \; fully-qualified # 👏🏿 - E2.0 clapping hands: dark skin tone\n1F64C - \; fully-qualified # 🙌 E2.0 raising hands\n1F64C 1F3FB - \; fully-qualified # 🙌🏻 E2.0 raising h - ands: light skin tone\n1F64C 1F3FC \; fully - -qualified # 🙌🏼 E2.0 raising hands: medium-light skin tone\n1F64 - C 1F3FD \; fully-qualified # 🙌🏽 E - 2.0 raising hands: medium skin tone\n1F64C 1F3FE - \; fully-qualified # 🙌🏾 E2.0 raising hands: medium-dark sk - in tone\n1F64C 1F3FF \; fully-qualified - # 🙌🏿 E2.0 raising hands: dark skin tone\n1F450 - \; fully-qualified # 👐 E2.0 open hands\n1F450 1F3F - B \; fully-qualified # 👐🏻 E2.0 op - en hands: light skin tone\n1F450 1F3FC \; f - ully-qualified # 👐🏼 E2.0 open hands: medium-light skin tone\n1F4 - 50 1F3FD \; fully-qualified # 👐🏽 - E2.0 open hands: medium skin tone\n1F450 1F3FE - \; fully-qualified # 👐🏾 E2.0 open hands: medium-dark skin to - ne\n1F450 1F3FF \; fully-qualified # - 👐🏿 E2.0 open hands: dark skin tone\n1F932 - \; fully-qualified # 🤲 E5.0 palms up together\n1F932 1F3 - FB \; fully-qualified # 🤲🏻 E5.0 p - alms up together: light skin tone\n1F932 1F3FC - \; fully-qualified # 🤲🏼 E5.0 palms up together: medium-light - skin tone\n1F932 1F3FD \; fully-qualified - # 🤲🏽 E5.0 palms up together: medium skin tone\n1F932 1F3FE - \; fully-qualified # 🤲🏾 E5.0 palms up - together: medium-dark skin tone\n1F932 1F3FF - \; fully-qualified # 🤲🏿 E5.0 palms up together: dark skin tone - \n1F91D \; fully-qualified # 🤝 - E4.0 handshake\n1F64F \; fully-quali - fied # 🙏 E2.0 folded hands\n1F64F 1F3FB - \; fully-qualified # 🙏🏻 E2.0 folded hands: light skin tone\n - 1F64F 1F3FC \; fully-qualified # 🙏 - 🏼 E2.0 folded hands: medium-light skin tone\n1F64F 1F3FD - \; fully-qualified # 🙏🏽 E2.0 folded hands: medi - um skin tone\n1F64F 1F3FE \; fully-qualifie - d # 🙏🏾 E2.0 folded hands: medium-dark skin tone\n1F64F 1F3FF - \; fully-qualified # 🙏🏿 E2.0 folded - hands: dark skin tone\n\n# subgroup: hand-prop\n270D FE0F - \; fully-qualified # ✍️ E2.0 writing hand\n270D - \; unqualified # ✍ E2.0 wri - ting hand\n270D 1F3FB \; fully-qualified - # ✍🏻 E2.0 writing hand: light skin tone\n270D 1F3FC - \; fully-qualified # ✍🏼 E2.0 writing hand: medi - um-light skin tone\n270D 1F3FD \; fully-qu - alified # ✍🏽 E2.0 writing hand: medium skin tone\n270D 1F3FE - \; fully-qualified # ✍🏾 E2.0 writing - hand: medium-dark skin tone\n270D 1F3FF \; - fully-qualified # ✍🏿 E2.0 writing hand: dark skin tone\n1F485 - \; fully-qualified # 💅 E2.0 nail - polish\n1F485 1F3FB \; fully-qualified - # 💅🏻 E2.0 nail polish: light skin tone\n1F485 1F3FC - \; fully-qualified # 💅🏼 E2.0 nail polish: medium - -light skin tone\n1F485 1F3FD \; fully-qual - ified # 💅🏽 E2.0 nail polish: medium skin tone\n1F485 1F3FE - \; fully-qualified # 💅🏾 E2.0 nail poli - sh: medium-dark skin tone\n1F485 1F3FF \; f - ully-qualified # 💅🏿 E2.0 nail polish: dark skin tone\n1F933 - \; fully-qualified # 🤳 E4.0 selfie - \n1F933 1F3FB \; fully-qualified # 🤳 - 🏻 E4.0 selfie: light skin tone\n1F933 1F3FC - \; fully-qualified # 🤳🏼 E4.0 selfie: medium-light skin tone\ - n1F933 1F3FD \; fully-qualified # 🤳 - 🏽 E4.0 selfie: medium skin tone\n1F933 1F3FE - \; fully-qualified # 🤳🏾 E4.0 selfie: medium-dark skin tone\ - n1F933 1F3FF \; fully-qualified # 🤳 - 🏿 E4.0 selfie: dark skin tone\n\n# subgroup: body-parts\n1F4AA - \; fully-qualified # 💪 E2.0 flexed bic - eps\n1F4AA 1F3FB \; fully-qualified # - 💪🏻 E2.0 flexed biceps: light skin tone\n1F4AA 1F3FC - \; fully-qualified # 💪🏼 E2.0 flexed biceps: mediu - m-light skin tone\n1F4AA 1F3FD \; fully-qua - lified # 💪🏽 E2.0 flexed biceps: medium skin tone\n1F4AA 1F3FE - \; fully-qualified # 💪🏾 E2.0 flexed - biceps: medium-dark skin tone\n1F4AA 1F3FF - \; fully-qualified # 💪🏿 E2.0 flexed biceps: dark skin tone\n1F9 - BE \; fully-qualified # 🦾 E12. - 1 mechanical arm\n1F9BF \; fully-qual - ified # 🦿 E12.1 mechanical leg\n1F9B5 - \; fully-qualified # 🦵 E11.0 leg\n1F9B5 1F3FB - \; fully-qualified # 🦵🏻 E11.0 leg: light skin - tone\n1F9B5 1F3FC \; fully-qualified # - 🦵🏼 E11.0 leg: medium-light skin tone\n1F9B5 1F3FD - \; fully-qualified # 🦵🏽 E11.0 leg: medium skin tone - \n1F9B5 1F3FE \; fully-qualified # 🦵 - 🏾 E11.0 leg: medium-dark skin tone\n1F9B5 1F3FF - \; fully-qualified # 🦵🏿 E11.0 leg: dark skin tone\n1F9B6 - \; fully-qualified # 🦶 E11.0 - foot\n1F9B6 1F3FB \; fully-qualified # - 🦶🏻 E11.0 foot: light skin tone\n1F9B6 1F3FC - \; fully-qualified # 🦶🏼 E11.0 foot: medium-light skin ton - e\n1F9B6 1F3FD \; fully-qualified # - 🦶🏽 E11.0 foot: medium skin tone\n1F9B6 1F3FE - \; fully-qualified # 🦶🏾 E11.0 foot: medium-dark skin ton - e\n1F9B6 1F3FF \; fully-qualified # - 🦶🏿 E11.0 foot: dark skin tone\n1F442 - \; fully-qualified # 👂 E2.0 ear\n1F442 1F3FB - \; fully-qualified # 👂🏻 E2.0 ear: light skin tone - \n1F442 1F3FC \; fully-qualified # 👂 - 🏼 E2.0 ear: medium-light skin tone\n1F442 1F3FD - \; fully-qualified # 👂🏽 E2.0 ear: medium skin tone\n1F44 - 2 1F3FE \; fully-qualified # 👂🏾 E - 2.0 ear: medium-dark skin tone\n1F442 1F3FF - \; fully-qualified # 👂🏿 E2.0 ear: dark skin tone\n1F9BB - \; fully-qualified # 🦻 E12.1 ear with - hearing aid\n1F9BB 1F3FB \; fully-qualifie - d # 🦻🏻 E12.1 ear with hearing aid: light skin tone\n1F9BB 1F3FC - \; fully-qualified # 🦻🏼 E12.1 ear - with hearing aid: medium-light skin tone\n1F9BB 1F3FD - \; fully-qualified # 🦻🏽 E12.1 ear with hearing aid: - medium skin tone\n1F9BB 1F3FE \; fully-qual - ified # 🦻🏾 E12.1 ear with hearing aid: medium-dark skin tone\n1F - 9BB 1F3FF \; fully-qualified # 🦻🏿 - E12.1 ear with hearing aid: dark skin tone\n1F443 - \; fully-qualified # 👃 E2.0 nose\n1F443 1F3FB - \; fully-qualified # 👃🏻 E2.0 nose: light - skin tone\n1F443 1F3FC \; fully-qualified - # 👃🏼 E2.0 nose: medium-light skin tone\n1F443 1F3FD - \; fully-qualified # 👃🏽 E2.0 nose: medium ski - n tone\n1F443 1F3FE \; fully-qualified - # 👃🏾 E2.0 nose: medium-dark skin tone\n1F443 1F3FF - \; fully-qualified # 👃🏿 E2.0 nose: dark skin tone\ - n1F9E0 \; fully-qualified # 🧠 - E5.0 brain\n1F9B7 \; fully-qualified - # 🦷 E11.0 tooth\n1F9B4 \; full - y-qualified # 🦴 E11.0 bone\n1F440 - \; fully-qualified # 👀 E2.0 eyes\n1F441 FE0F - \; fully-qualified # 👁️ E2.0 eye\n1F441 - \; unqualified # 👁 E2.0 eye\n1F445 - \; fully-qualified # 👅 E2.0 tong - ue\n1F444 \; fully-qualified # - 👄 E2.0 mouth\n\n# subgroup: person\n1F476 - \; fully-qualified # 👶 E2.0 baby\n1F476 1F3FB - \; fully-qualified # 👶🏻 E2.0 baby: light skin - tone\n1F476 1F3FC \; fully-qualified # - 👶🏼 E2.0 baby: medium-light skin tone\n1F476 1F3FD - \; fully-qualified # 👶🏽 E2.0 baby: medium skin tone - \n1F476 1F3FE \; fully-qualified # 👶 - �� E2.0 baby: medium-dark skin tone\n1F476 1F3FF - \; fully-qualified # 👶🏿 E2.0 baby: dark skin tone\n1F9 - D2 \; fully-qualified # 🧒 E5.0 - child\n1F9D2 1F3FB \; fully-qualified - # 🧒🏻 E5.0 child: light skin tone\n1F9D2 1F3FC - \; fully-qualified # 🧒🏼 E5.0 child: medium-light skin t - one\n1F9D2 1F3FD \; fully-qualified # - 🧒🏽 E5.0 child: medium skin tone\n1F9D2 1F3FE - \; fully-qualified # 🧒🏾 E5.0 child: medium-dark skin ton - e\n1F9D2 1F3FF \; fully-qualified # - 🧒🏿 E5.0 child: dark skin tone\n1F466 - \; fully-qualified # 👦 E2.0 boy\n1F466 1F3FB - \; fully-qualified # 👦🏻 E2.0 boy: light skin tone - \n1F466 1F3FC \; fully-qualified # 👦 - 🏼 E2.0 boy: medium-light skin tone\n1F466 1F3FD - \; fully-qualified # 👦🏽 E2.0 boy: medium skin tone\n1F46 - 6 1F3FE \; fully-qualified # ��🏾 - E2.0 boy: medium-dark skin tone\n1F466 1F3FF - \; fully-qualified # 👦🏿 E2.0 boy: dark skin tone\n1F467 - \; fully-qualified # 👧 E2.0 girl\n1 - F467 1F3FB \; fully-qualified # 👧 - 🏻 E2.0 girl: light skin tone\n1F467 1F3FC - \; fully-qualified # 👧🏼 E2.0 girl: medium-light skin tone\n1F4 - 67 1F3FD \; fully-qualified # 👧🏽 - E2.0 girl: medium skin tone\n1F467 1F3FE \; - fully-qualified # 👧�� E2.0 girl: medium-dark skin tone\n1F467 - 1F3FF \; fully-qualified # 👧🏿 E2. - 0 girl: dark skin tone\n1F9D1 \; full - y-qualified # 🧑 E5.0 person\n1F9D1 1F3FB - \; fully-qualified # 🧑🏻 E5.0 person: light skin tone\n1F9D1 - 1F3FC \; fully-qualified # 🧑🏼 E5 - .0 person: medium-light skin tone\n1F9D1 1F3FD - \; fully-qualified # 🧑🏽 E5.0 person: medium skin tone\n1F9D1 - 1F3FE \; fully-qualified # 🧑🏾 E5 - .0 person: medium-dark skin tone\n1F9D1 1F3FF - \; fully-qualified # 🧑🏿 E5.0 person: dark skin tone\n1F471 - \; fully-qualified # 👱 E2.0 pers - on: blond hair\n1F471 1F3FB \; fully-qualif - ied # 👱🏻 E2.0 person: light skin tone\, blond hair\n1F471 1F3FC - \; fully-qualified # 👱🏼 E2.0 pers - on: medium-light skin tone\, blond hair\n1F471 1F3FD - \; fully-qualified # 👱🏽 E2.0 person: medium skin tone\ - , blond hair\n1F471 1F3FE \; fully-qualifie - d # 👱🏾 E2.0 person: medium-dark skin tone\, blond hair\n1F471 1F - 3FF \; fully-qualified # 👱🏿 E2.0 - person: dark skin tone\, blond hair\n1F468 - \; fully-qualified # 👨 E2.0 man\n1F468 1F3FB - \; fully-qualified # 👨🏻 E2.0 man: light skin tone - \n1F468 1F3FC \; fully-qualified # 👨 - 🏼 E2.0 man: medium-light skin tone\n1F468 1F3FD - \; fully-qualified # 👨🏽 E2.0 man: medium skin tone\n1F46 - 8 1F3FE \; fully-qualified # 👨🏾 E - 2.0 man: medium-dark skin tone\n1F468 1F3FF - \; fully-qualified # 👨🏿 E2.0 man: dark skin tone\n1F9D4 - \; fully-qualified # �� E5.0 man: be - ard\n1F9D4 1F3FB \; fully-qualified # - 🧔🏻 E5.0 man: light skin tone\, beard\n1F9D4 1F3FC - \; fully-qualified # 🧔🏼 E5.0 man: medium-light skin - tone\, beard\n1F9D4 1F3FD \; fully-qualifi - ed # 🧔🏽 E5.0 man: medium skin tone\, beard\n1F9D4 1F3FE - \; fully-qualified # 🧔🏾 E5.0 man: medium- - dark skin tone\, beard\n1F9D4 1F3FF \; full - y-qualified # 🧔🏿 E5.0 man: dark skin tone\, beard\n1F468 200D 1F - 9B0 \; fully-qualified # 👨‍🦰 E11.0 m - an: red hair\n1F468 1F3FB 200D 1F9B0 \; fully-qualifie - d # 👨🏻‍🦰 E11.0 man: light skin tone\, red hair\n1F468 1F3FC - 200D 1F9B0 \; fully-qualified # 👨🏼‍🦰 E - 11.0 man: medium-light skin tone\, red hair\n1F468 1F3FD 200D 1F9B0 - \; fully-qualified # 👨��‍🦰 E11.0 man: medium - skin tone\, red hair\n1F468 1F3FE 200D 1F9B0 \; fully - -qualified # 👨🏾‍🦰 E11.0 man: medium-dark skin tone\, red ha - ir\n1F468 1F3FF 200D 1F9B0 \; fully-qualified # - 👨🏿‍🦰 E11.0 man: dark skin tone\, red hair\n1F468 200D 1F9B1 - \; fully-qualified # 👨‍🦱 E11.0 man: cur - ly hair\n1F468 1F3FB 200D 1F9B1 \; fully-qualified - # 👨🏻‍🦱 E11.0 man: light skin tone\, curly hair\n1F468 1F3FC 20 - 0D 1F9B1 \; fully-qualified # 👨🏼‍🦱 E11. - 0 man: medium-light skin tone\, curly hair\n1F468 1F3FD 200D 1F9B1 - \; fully-qualified # 👨🏽‍🦱 E11.0 man: medium sk - in tone\, curly hair\n1F468 1F3FE 200D 1F9B1 \; fully- - qualified # 👨🏾‍🦱 E11.0 man: medium-dark skin tone\, curly h - air\n1F468 1F3FF 200D 1F9B1 \; fully-qualified # - 👨🏿‍🦱 E11.0 man: dark skin tone\, curly hair\n1F468 200D 1F9B3 - \; fully-qualified # 👨‍🦳 E11.0 man: w - hite hair\n1F468 1F3FB 200D 1F9B3 \; fully-qualified - # 👨🏻‍🦳 E11.0 man: light skin tone\, white hair\n1F468 1F3FC - 200D 1F9B3 \; fully-qualified # 👨🏼‍🦳 E1 - 1.0 man: medium-light skin tone\, white hair\n1F468 1F3FD 200D 1F9B3 - \; fully-qualified # 👨🏽‍🦳 E11.0 man: medium - skin tone\, white hair\n1F468 1F3FE 200D 1F9B3 \; full - y-qualified # ��🏾‍🦳 E11.0 man: medium-dark skin tone\, whi - te hair\n1F468 1F3FF 200D 1F9B3 \; fully-qualified - # 👨🏿‍🦳 E11.0 man: dark skin tone\, white hair\n1F468 200D 1F9B - 2 \; fully-qualified # 👨‍🦲 E11.0 man - : bald\n1F468 1F3FB 200D 1F9B2 \; fully-qualified - # 👨🏻‍🦲 E11.0 man: light skin tone\, bald\n1F468 1F3FC 200D 1F9B - 2 \; fully-qualified # 👨🏼‍🦲 E11.0 man: - medium-light skin tone\, bald\n1F468 1F3FD 200D 1F9B2 - \; fully-qualified # 👨🏽‍🦲 E11.0 man: medium skin tone\, bal - d\n1F468 1F3FE 200D 1F9B2 \; fully-qualified # - 👨🏾‍🦲 E11.0 man: medium-dark skin tone\, bald\n1F468 1F3FF 200D - 1F9B2 \; fully-qualified # 👨🏿‍🦲 E11.0 m - an: dark skin tone\, bald\n1F469 \; f - ully-qualified # 👩 E2.0 woman\n1F469 1F3FB - \; fully-qualified # 👩🏻 E2.0 woman: light skin tone\n1F46 - 9 1F3FC \; fully-qualified # 👩🏼 E - 2.0 woman: medium-light skin tone\n1F469 1F3FD - \; fully-qualified # 👩🏽 E2.0 woman: medium skin tone\n1F469 - 1F3FE \; fully-qualified # 👩🏾 E2. - 0 woman: medium-dark skin tone\n1F469 1F3FF - \; fully-qualified # 👩🏿 E2.0 woman: dark skin tone\n1F469 200D - 1F9B0 \; fully-qualified # 👩‍🦰 E11.0 - woman: red hair\n1F469 1F3FB 200D 1F9B0 \; fully-qual - ified # 👩🏻‍🦰 E11.0 woman: light skin tone\, red hair\n1F469 - 1F3FC 200D 1F9B0 \; fully-qualified # 👩🏼‍ - 🦰 E11.0 woman: medium-light skin tone\, red hair\n1F469 1F3FD 200D 1F9B - 0 \; fully-qualified # 👩🏽‍🦰 E11.0 woman - : medium skin tone\, red hair\n1F469 1F3FE 200D 1F9B0 - \; fully-qualified # 👩🏾‍🦰 E11.0 woman: medium-dark skin ton - e\, red hair\n1F469 1F3FF 200D 1F9B0 \; fully-qualifie - d # 👩🏿‍🦰 E11.0 woman: dark skin tone\, red hair\n1F9D1 200D - 1F9B0 \; fully-qualified # 🧑‍🦰 E12. - 1 person: red hair\n1F9D1 1F3FB 200D 1F9B0 \; fully-qu - alified # 🧑🏻‍🦰 E12.1 person: light skin tone\, red hair\n1F - 9D1 1F3FC 200D 1F9B0 \; fully-qualified # 🧑🏼 - ‍🦰 E12.1 person: medium-light skin tone\, red hair\n1F9D1 1F3FD 200D - 1F9B0 \; fully-qualified # 🧑🏽‍🦰 E12.1 p - erson: medium skin tone\, red hair\n1F9D1 1F3FE 200D 1F9B0 - \; fully-qualified # 🧑🏾‍🦰 E12.1 person: medium-dark sk - in tone\, red hair\n1F9D1 1F3FF 200D 1F9B0 \; fully-qu - alified # 🧑🏿‍🦰 E12.1 person: dark skin tone\, red hair\n1F4 - 69 200D 1F9B1 \; fully-qualified # 👩‍ - 🦱 E11.0 woman: curly hair\n1F469 1F3FB 200D 1F9B1 \ - ; fully-qualified # 👩🏻‍🦱 E11.0 woman: light skin tone\, cur - ly hair\n1F469 1F3FC 200D 1F9B1 \; fully-qualified - # 👩🏼‍🦱 E11.0 woman: medium-light skin tone\, curly hair\n1F469 - 1F3FD 200D 1F9B1 \; fully-qualified # 👩🏽‍ - 🦱 E11.0 woman: medium skin tone\, curly hair\n1F469 1F3FE 200D 1F9B1 - \; fully-qualified # 👩🏾‍🦱 E11.0 woman: me - dium-dark skin tone\, curly hair\n1F469 1F3FF 200D 1F9B1 - \; fully-qualified # 👩🏿‍🦱 E11.0 woman: dark skin tone\, - curly hair\n1F9D1 200D 1F9B1 \; fully-qualified - # 🧑‍🦱 E12.1 person: curly hair\n1F9D1 1F3FB 200D 1F9B1 - \; fully-qualified # 🧑🏻‍🦱 E12.1 person: light - skin tone\, curly hair\n1F9D1 1F3FC 200D 1F9B1 \; full - y-qualified # 🧑🏼‍🦱 E12.1 person: medium-light skin tone\, c - urly hair\n1F9D1 1F3FD 200D 1F9B1 \; fully-qualified - # 🧑🏽‍🦱 E12.1 person: medium skin tone\, curly hair\n1F9D1 1F - 3FE 200D 1F9B1 \; fully-qualified # 🧑🏾‍ - 🦱 E12.1 person: medium-dark skin tone\, curly hair\n1F9D1 1F3FF 200D 1F - 9B1 \; fully-qualified # 🧑🏿‍🦱 E12.1 per - son: dark skin tone\, curly hair\n1F469 200D 1F9B3 - \; fully-qualified # 👩‍🦳 E11.0 woman: white hair\n1F469 1F3 - FB 200D 1F9B3 \; fully-qualified # 👩🏻‍🦳 - E11.0 woman: light skin tone\, white hair\n1F469 1F3FC 200D 1F9B3 - \; fully-qualified # 👩🏼‍🦳 E11.0 woman: medium- - light skin tone\, white hair\n1F469 1F3FD 200D 1F9B3 \ - ; fully-qualified # 👩🏽‍🦳 E11.0 woman: medium skin tone\, wh - ite hair\n1F469 1F3FE 200D 1F9B3 \; fully-qualified - # 👩🏾‍🦳 E11.0 woman: medium-dark skin tone\, white hair\n1F469 - 1F3FF 200D 1F9B3 \; fully-qualified # 👩🏿‍ - 🦳 E11.0 woman: dark skin tone\, white hair\n1F9D1 200D 1F9B3 - \; fully-qualified # 🧑‍🦳 E12.1 person: white h - air\n1F9D1 1F3FB 200D 1F9B3 \; fully-qualified # - 🧑🏻‍🦳 E12.1 person: light skin tone\, white hair\n1F9D1 1F3FC 20 - 0D 1F9B3 \; fully-qualified # 🧑🏼‍🦳 E12. - 1 person: medium-light skin tone\, white hair\n1F9D1 1F3FD 200D 1F9B3 - \; fully-qualified # 🧑🏽‍🦳 E12.1 person: med - ium skin tone\, white hair\n1F9D1 1F3FE 200D 1F9B3 \; - fully-qualified # 🧑🏾‍🦳 E12.1 person: medium-dark skin tone\ - , white hair\n1F9D1 1F3FF 200D 1F9B3 \; fully-qualifie - d # 🧑🏿‍🦳 E12.1 person: dark skin tone\, white hair\n1F469 2 - 00D 1F9B2 \; fully-qualified # 👩‍🦲 E - 11.0 woman: bald\n1F469 1F3FB 200D 1F9B2 \; fully-qual - ified # 👩🏻‍🦲 E11.0 woman: light skin tone\, bald\n1F469 1F3 - FC 200D 1F9B2 \; fully-qualified # 👩🏼‍🦲 - E11.0 woman: medium-light skin tone\, bald\n1F469 1F3FD 200D 1F9B2 - \; fully-qualified # 👩🏽‍🦲 E11.0 woman: medium - skin tone\, bald\n1F469 1F3FE 200D 1F9B2 \; fully-qua - lified # 👩🏾‍🦲 E11.0 woman: medium-dark skin tone\, bald\n1F - 469 1F3FF 200D 1F9B2 \; fully-qualified # 👩🏿 - ‍🦲 E11.0 woman: dark skin tone\, bald\n1F9D1 200D 1F9B2 - \; fully-qualified # 🧑‍🦲 E12.1 person: bald\n1F9D - 1 1F3FB 200D 1F9B2 \; fully-qualified # 🧑🏻 - ‍🦲 E12.1 person: light skin tone\, bald\n1F9D1 1F3FC 200D 1F9B2 - \; fully-qualified # 🧑🏼‍🦲 E12.1 person: medi - um-light skin tone\, bald\n1F9D1 1F3FD 200D 1F9B2 \; f - ully-qualified # 🧑🏽‍🦲 E12.1 person: medium skin tone\, bald - \n1F9D1 1F3FE 200D 1F9B2 \; fully-qualified # 🧑 - 🏾‍🦲 E12.1 person: medium-dark skin tone\, bald\n1F9D1 1F3FF 200D 1 - F9B2 \; fully-qualified # ��🏿‍🦲 E12.1 - person: dark skin tone\, bald\n1F471 200D 2640 FE0F - \; fully-qualified # 👱‍♀️ E4.0 woman: blond hair\n1F471 200D - 2640 \; minimally-qualified # 👱‍♀ E4.0 w - oman: blond hair\n1F471 1F3FB 200D 2640 FE0F \; fully-qual - ified # 👱🏻‍♀️ E4.0 woman: light skin tone\, blond hair\n1F - 471 1F3FB 200D 2640 \; minimally-qualified # 👱🏻 - ‍♀ E4.0 woman: light skin tone\, blond hair\n1F471 1F3FC 200D 2640 FE0 - F \; fully-qualified # 👱🏼‍♀️ E4.0 woman: m - edium-light skin tone\, blond hair\n1F471 1F3FC 200D 2640 - \; minimally-qualified # 👱🏼‍♀ E4.0 woman: medium-light skin - tone\, blond hair\n1F471 1F3FD 200D 2640 FE0F \; fully-qu - alified # 👱🏽‍♀️ E4.0 woman: medium skin tone\, blond hair\ - n1F471 1F3FD 200D 2640 \; minimally-qualified # 👱 - 🏽‍♀ E4.0 woman: medium skin tone\, blond hair\n1F471 1F3FE 200D 264 - 0 FE0F \; fully-qualified # 👱🏾‍♀️ E4.0 wom - an: medium-dark skin tone\, blond hair\n1F471 1F3FE 200D 2640 - \; minimally-qualified # 👱🏾‍♀ E4.0 woman: medium-dark s - kin tone\, blond hair\n1F471 1F3FF 200D 2640 FE0F \; fully - -qualified # 👱🏿‍♀️ E4.0 woman: dark skin tone\, blond hair - \n1F471 1F3FF 200D 2640 \; minimally-qualified # 👱 - 🏿‍♀ E4.0 woman: dark skin tone\, blond hair\n1F471 200D 2642 FE0F - \; fully-qualified # 👱‍♂️ E4.0 man: blon - d hair\n1F471 200D 2642 \; minimally-qualified - # 👱‍♂ E4.0 man: blond hair\n1F471 1F3FB 200D 2642 FE0F - \; fully-qualified # 👱🏻‍♂️ E4.0 man: light skin tone\, - blond hair\n1F471 1F3FB 200D 2642 \; minimally-quali - fied # 👱🏻‍♂ E4.0 man: light skin tone\, blond hair\n1F471 1F3FC - 200D 2642 FE0F \; fully-qualified # 👱🏼‍♂️ - E4.0 man: medium-light skin tone\, blond hair\n1F471 1F3FC 200D 2642 - \; minimally-qualified # 👱🏼‍♂ E4.0 man: medium-l - ight skin tone\, blond hair\n1F471 1F3FD 200D 2642 FE0F \; - fully-qualified # 👱🏽‍♂️ E4.0 man: medium skin tone\, blon - d hair\n1F471 1F3FD 200D 2642 \; minimally-qualified - # 👱🏽‍♂ E4.0 man: medium skin tone\, blond hair\n1F471 1F3FE 200D - 2642 FE0F \; fully-qualified # 👱🏾‍♂️ E4.0 - man: medium-dark skin tone\, blond hair\n1F471 1F3FE 200D 2642 - \; minimally-qualified # 👱🏾‍♂ E4.0 man: medium-dark s - kin tone\, blond hair\n1F471 1F3FF 200D 2642 FE0F \; fully - -qualified # 👱🏿‍♂️ E4.0 man: dark skin tone\, blond hair\n - 1F471 1F3FF 200D 2642 \; minimally-qualified # 👱 - 🏿‍♂ E4.0 man: dark skin tone\, blond hair\n1F9D3 - \; fully-qualified # 🧓 E5.0 older person\n1F9D3 - 1F3FB \; fully-qualified # 🧓🏻 E5. - 0 older person: light skin tone\n1F9D3 1F3FC - \; fully-qualified # 🧓🏼 E5.0 older person: medium-light skin t - one\n1F9D3 1F3FD \; fully-qualified # - 🧓🏽 E5.0 older person: medium skin tone\n1F9D3 1F3FE - \; fully-qualified # 🧓🏾 E5.0 older person: medium - -dark skin tone\n1F9D3 1F3FF \; fully-quali - fied # 🧓🏿 E5.0 older person: dark skin tone\n1F474 - \; fully-qualified # 👴 E2.0 old man\n1F474 - 1F3FB \; fully-qualified # 👴🏻 E2. - 0 old man: light skin tone\n1F474 1F3FC \; - fully-qualified # 👴🏼 E2.0 old man: medium-light skin tone\n1F474 - 1F3FD \; fully-qualified # 👴🏽 E2 - .0 old man: medium skin tone\n1F474 1F3FE \ - ; fully-qualified # 👴🏾 E2.0 old man: medium-dark skin tone\n1F47 - 4 1F3FF \; fully-qualified # 👴🏿 E - 2.0 old man: dark skin tone\n1F475 \; - fully-qualified # 👵 E2.0 old woman\n1F475 1F3FB - \; fully-qualified # 👵🏻 E2.0 old woman: light skin - tone\n1F475 1F3FC \; fully-qualified # - 👵🏼 E2.0 old woman: medium-light skin tone\n1F475 1F3FD - \; fully-qualified # 👵🏽 E2.0 old woman: medium - skin tone\n1F475 1F3FE \; fully-qualified - # 👵🏾 E2.0 old woman: medium-dark skin tone\n1F475 1F3FF - \; fully-qualified # 👵🏿 E2.0 old woman: d - ark skin tone\n\n# subgroup: person-gesture\n1F64D - \; fully-qualified # 🙍 E2.0 person frowning\n1F64D 1F - 3FB \; fully-qualified # 🙍🏻 E2.0 - person frowning: light skin tone\n1F64D 1F3FC - \; fully-qualified # 🙍🏼 E2.0 person frowning: medium-light sk - in tone\n1F64D 1F3FD \; fully-qualified - # 🙍🏽 E2.0 person frowning: medium skin tone\n1F64D 1F3FE - \; fully-qualified # 🙍🏾 E2.0 person frownin - g: medium-dark skin tone\n1F64D 1F3FF \; fu - lly-qualified # 🙍🏿 E2.0 person frowning: dark skin tone\n1F64D 2 - 00D 2642 FE0F \; fully-qualified # 🙍‍♂️ - E4.0 man frowning\n1F64D 200D 2642 \; minimall - y-qualified # 🙍‍♂ E4.0 man frowning\n1F64D 1F3FB 200D 2642 FE0F - \; fully-qualified # 🙍🏻‍♂️ E4.0 man frowning: - light skin tone\n1F64D 1F3FB 200D 2642 \; minimally- - qualified # 🙍🏻‍♂ E4.0 man frowning: light skin tone\n1F64D 1F3FC - 200D 2642 FE0F \; fully-qualified # 🙍🏼‍♂️ - E4.0 man frowning: medium-light skin tone\n1F64D 1F3FC 200D 2642 - \; minimally-qualified # ��🏼‍♂ E4.0 man frowning: - medium-light skin tone\n1F64D 1F3FD 200D 2642 FE0F \; full - y-qualified # 🙍🏽‍♂️ E4.0 man frowning: medium skin tone\n1 - F64D 1F3FD 200D 2642 \; minimally-qualified # 🙍 - 🏽‍♂ E4.0 man frowning: medium skin tone\n1F64D 1F3FE 200D 2642 FE0F - \; fully-qualified # 🙍🏾‍♂️ E4.0 man frown - ing: medium-dark skin tone\n1F64D 1F3FE 200D 2642 \; - minimally-qualified # 🙍🏾‍♂ E4.0 man frowning: medium-dark skin t - one\n1F64D 1F3FF 200D 2642 FE0F \; fully-qualified # - 🙍🏿‍♂️ E4.0 man frowning: dark skin tone\n1F64D 1F3FF 200D 2642 - \; minimally-qualified # 🙍🏿‍♂ E4.0 man fro - wning: dark skin tone\n1F64D 200D 2640 FE0F \; fully - -qualified # 🙍‍♀️ E4.0 woman frowning\n1F64D 200D 2640 - \; minimally-qualified # 🙍‍♀ E4.0 woman frowni - ng\n1F64D 1F3FB 200D 2640 FE0F \; fully-qualified # - 🙍🏻‍♀️ E4.0 woman frowning: light skin tone\n1F64D 1F3FB 200D 2 - 640 \; minimally-qualified # 🙍🏻‍♀ E4.0 woma - n frowning: light skin tone\n1F64D 1F3FC 200D 2640 FE0F \; - fully-qualified # 🙍🏼‍♀️ E4.0 woman frowning: medium-light - skin tone\n1F64D 1F3FC 200D 2640 \; minimally-qualif - ied # 🙍🏼‍♀ E4.0 woman frowning: medium-light skin tone\n1F64D 1F - 3FD 200D 2640 FE0F \; fully-qualified # 🙍🏽‍♀ - ️ E4.0 woman frowning: medium skin tone\n1F64D 1F3FD 200D 2640 - \; minimally-qualified # 🙍🏽‍♀ E4.0 woman frowning: m - edium skin tone\n1F64D 1F3FE 200D 2640 FE0F \; fully-quali - fied # 🙍🏾‍♀️ E4.0 woman frowning: medium-dark skin tone\n1 - F64D 1F3FE 200D 2640 \; minimally-qualified # 🙍 - 🏾‍♀ E4.0 woman frowning: medium-dark skin tone\n1F64D 1F3FF 200D 26 - 40 FE0F \; fully-qualified # 🙍🏿‍♀️ E4.0 wo - man frowning: dark skin tone\n1F64D 1F3FF 200D 2640 \ - ; minimally-qualified # 🙍🏿‍♀ E4.0 woman frowning: dark skin tone - \n1F64E \; fully-qualified # 🙎 - E2.0 person pouting\n1F64E 1F3FB \; fully- - qualified # 🙎🏻 E2.0 person pouting: light skin tone\n1F64E 1F3FC - \; fully-qualified # 🙎🏼 E2.0 per - son pouting: medium-light skin tone\n1F64E 1F3FD - \; fully-qualified # 🙎🏽 E2.0 person pouting: medium skin t - one\n1F64E 1F3FE \; fully-qualified # - 🙎🏾 E2.0 person pouting: medium-dark skin tone\n1F64E 1F3FF - \; fully-qualified # 🙎🏿 E2.0 person poutin - g: dark skin tone\n1F64E 200D 2642 FE0F \; fully-qua - lified # 🙎‍♂️ E4.0 man pouting\n1F64E 200D 2642 - \; minimally-qualified # 🙎‍♂ E4.0 man pouting\n1F64E - 1F3FB 200D 2642 FE0F \; fully-qualified # 🙎🏻‍ - ♂️ E4.0 man pouting: light skin tone\n1F64E 1F3FB 200D 2642 - \; minimally-qualified # 🙎🏻‍♂ E4.0 man pouting: light - skin tone\n1F64E 1F3FC 200D 2642 FE0F \; fully-qualified - # 🙎🏼‍♂️ E4.0 man pouting: medium-light skin tone\n1F64E 1F - 3FC 200D 2642 \; minimally-qualified # 🙎🏼‍♂ - E4.0 man pouting: medium-light skin tone\n1F64E 1F3FD 200D 2642 FE0F - \; fully-qualified # 🙎��‍♂️ E4.0 man pouting: - medium skin tone\n1F64E 1F3FD 200D 2642 \; minimally - -qualified # 🙎🏽‍♂ E4.0 man pouting: medium skin tone\n1F64E 1F3F - E 200D 2642 FE0F \; fully-qualified # 🙎🏾‍♂ - ️ E4.0 man pouting: medium-dark skin tone\n1F64E 1F3FE 200D 2642 - \; minimally-qualified # 🙎🏾‍♂ E4.0 man pouting: me - dium-dark skin tone\n1F64E 1F3FF 200D 2642 FE0F \; fully-q - ualified # 🙎🏿‍♂️ E4.0 man pouting: dark skin tone\n1F64E 1 - F3FF 200D 2642 \; minimally-qualified # 🙎🏿‍ - ♂ E4.0 man pouting: dark skin tone\n1F64E 200D 2640 FE0F - \; fully-qualified # 🙎‍♀️ E4.0 woman pouting\n1F64E 20 - 0D 2640 \; minimally-qualified # 🙎‍♀ E4. - 0 woman pouting\n1F64E 1F3FB 200D 2640 FE0F \; fully-quali - fied # 🙎🏻‍♀️ E4.0 woman pouting: light skin tone\n1F64E 1F - 3FB 200D 2640 \; minimally-qualified # 🙎��‍ - ♀ E4.0 woman pouting: light skin tone\n1F64E 1F3FC 200D 2640 FE0F - \; fully-qualified # 🙎🏼‍♀️ E4.0 woman pouting: m - edium-light skin tone\n1F64E 1F3FC 200D 2640 \; minim - ally-qualified # 🙎🏼‍♀ E4.0 woman pouting: medium-light skin tone - \n1F64E 1F3FD 200D 2640 FE0F \; fully-qualified # 🙎 - 🏽‍♀️ E4.0 woman pouting: medium skin tone\n1F64E 1F3FD 200D 2640 - \; minimally-qualified # 🙎🏽‍♀ E4.0 woman po - uting: medium skin tone\n1F64E 1F3FE 200D 2640 FE0F \; ful - ly-qualified # 🙎🏾‍♀️ E4.0 woman pouting: medium-dark skin - tone\n1F64E 1F3FE 200D 2640 \; minimally-qualified # - 🙎🏾‍♀ E4.0 woman pouting: medium-dark skin tone\n1F64E 1F3FF 200D - 2640 FE0F \; fully-qualified # 🙎🏿‍♀️ E4.0 - woman pouting: dark skin tone\n1F64E 1F3FF 200D 2640 - \; minimally-qualified # 🙎🏿‍♀ E4.0 woman pouting: dark skin ton - e\n1F645 \; fully-qualified # - 🙅 E2.0 person gesturing NO\n1F645 1F3FB - \; fully-qualified # 🙅🏻 E2.0 person gesturing NO: light skin ton - e\n1F645 1F3FC \; fully-qualified # - 🙅🏼 E2.0 person gesturing NO: medium-light skin tone\n1F645 1F3FD - \; fully-qualified # 🙅🏽 E2.0 person - gesturing NO: medium skin tone\n1F645 1F3FE - \; fully-qualified # 🙅🏾 E2.0 person gesturing NO: medium-dark s - kin tone\n1F645 1F3FF \; fully-qualified - # 🙅🏿 E2.0 person gesturing NO: dark skin tone\n1F645 200D 2642 FE0 - F \; fully-qualified # 🙅‍♂️ E4.0 man ge - sturing NO\n1F645 200D 2642 \; minimally-qualif - ied # 🙅‍♂ E4.0 man gesturing NO\n1F645 1F3FB 200D 2642 FE0F - \; fully-qualified # 🙅🏻‍♂️ E4.0 man gesturing NO: - light skin tone\n1F645 1F3FB 200D 2642 \; minimally- - qualified # 🙅🏻‍♂ E4.0 man gesturing NO: light skin tone\n1F645 1 - F3FC 200D 2642 FE0F \; fully-qualified # 🙅🏼‍ - ♂️ E4.0 man gesturing NO: medium-light skin tone\n1F645 1F3FC 200D 264 - 2 \; minimally-qualified # 🙅🏼‍♂ E4.0 man ge - sturing NO: medium-light skin tone\n1F645 1F3FD 200D 2642 FE0F - \; fully-qualified # 🙅🏽‍♂️ E4.0 man gesturing NO: med - ium skin tone\n1F645 1F3FD 200D 2642 \; minimally-qua - lified # 🙅🏽‍♂ E4.0 man gesturing NO: medium skin tone\n1F645 1F3 - FE 200D 2642 FE0F \; fully-qualified # 🙅🏾‍♂ - ️ E4.0 man gesturing NO: medium-dark skin tone\n1F645 1F3FE 200D 2642 - \; minimally-qualified # 🙅🏾‍♂ E4.0 man gestur - ing NO: medium-dark skin tone\n1F645 1F3FF 200D 2642 FE0F - \; fully-qualified # 🙅🏿‍♂️ E4.0 man gesturing NO: dark ski - n tone\n1F645 1F3FF 200D 2642 \; minimally-qualified - # 🙅🏿‍♂ E4.0 man gesturing NO: dark skin tone\n1F645 200D 2640 FE - 0F \; fully-qualified # 🙅‍♀️ E4.0 woman - gesturing NO\n1F645 200D 2640 \; minimally-qua - lified # 🙅‍♀ E4.0 woman gesturing NO\n1F645 1F3FB 200D 2640 FE0F - \; fully-qualified # 🙅🏻‍♀️ E4.0 woman gestur - ing NO: light skin tone\n1F645 1F3FB 200D 2640 \; min - imally-qualified # 🙅🏻‍♀ E4.0 woman gesturing NO: light skin tone - \n1F645 1F3FC 200D 2640 FE0F \; fully-qualified # 🙅 - 🏼‍♀️ E4.0 woman gesturing NO: medium-light skin tone\n1F645 1F3FC - 200D 2640 \; minimally-qualified # 🙅🏼‍♀ E4 - .0 woman gesturing NO: medium-light skin tone\n1F645 1F3FD 200D 2640 FE0F - \; fully-qualified # 🙅🏽‍♀️ E4.0 woman gest - uring NO: medium skin tone\n1F645 1F3FD 200D 2640 \; - minimally-qualified # ��🏽‍♀ E4.0 woman gesturing NO: medium ski - n tone\n1F645 1F3FE 200D 2640 FE0F \; fully-qualified - # 🙅🏾‍♀️ E4.0 woman gesturing NO: medium-dark skin tone\n1F645 - 1F3FE 200D 2640 \; minimally-qualified # 🙅🏾‍ - ♀ E4.0 woman gesturing NO: medium-dark skin tone\n1F645 1F3FF 200D 2640 - FE0F \; fully-qualified # 🙅🏿‍♀️ E4.0 woman - gesturing NO: dark skin tone\n1F645 1F3FF 200D 2640 - \; minimally-qualified # 🙅🏿‍♀ E4.0 woman gesturing NO: dark skin - tone\n1F646 \; fully-qualified # - 🙆 E2.0 person gesturing OK\n1F646 1F3FB - \; fully-qualified # 🙆🏻 E2.0 person gesturing OK: light skin to - ne\n1F646 1F3FC \; fully-qualified # - 🙆🏼 E2.0 person gesturing OK: medium-light skin tone\n1F646 1F3FD - \; fully-qualified # 🙆🏽 E2.0 person - gesturing OK: medium skin tone\n1F646 1F3FE - \; fully-qualified # 🙆🏾 E2.0 person gesturing OK: medium-dark s - kin tone\n1F646 1F3FF \; fully-qualified - # 🙆🏿 E2.0 person gesturing OK: dark skin tone\n1F646 200D 2642 FE0 - F \; fully-qualified # 🙆‍♂️ E4.0 man ge - sturing OK\n1F646 200D 2642 \; minimally-qualif - ied # 🙆‍♂ E4.0 man gesturing OK\n1F646 1F3FB 200D 2642 FE0F - \; fully-qualified # 🙆🏻‍♂️ E4.0 man gesturing OK: - light skin tone\n1F646 1F3FB 200D 2642 \; minimally- - qualified # 🙆🏻‍♂ E4.0 man gesturing OK: light skin tone\n1F646 1 - F3FC 200D 2642 FE0F \; fully-qualified # 🙆🏼‍ - ♂️ E4.0 man gesturing OK: medium-light skin tone\n1F646 1F3FC 200D 264 - 2 \; minimally-qualified # 🙆🏼‍♂ E4.0 man ge - sturing OK: medium-light skin tone\n1F646 1F3FD 200D 2642 FE0F - \; fully-qualified # 🙆🏽‍♂️ E4.0 man gesturing OK: med - ium skin tone\n1F646 1F3FD 200D 2642 \; minimally-qua - lified # 🙆🏽‍♂ E4.0 man gesturing OK: medium skin tone\n1F646 1F3 - FE 200D 2642 FE0F \; fully-qualified # 🙆🏾‍♂ - ️ E4.0 man gesturing OK: medium-dark skin tone\n1F646 1F3FE 200D 2642 - \; minimally-qualified # 🙆🏾‍♂ E4.0 man gestur - ing OK: medium-dark skin tone\n1F646 1F3FF 200D 2642 FE0F - \; fully-qualified # 🙆🏿‍♂️ E4.0 man gesturing OK: dark ski - n tone\n1F646 1F3FF 200D 2642 \; minimally-qualified - # 🙆��‍♂ E4.0 man gesturing OK: dark skin tone\n1F646 200D 2640 - FE0F \; fully-qualified # 🙆‍♀️ E4.0 wom - an gesturing OK\n1F646 200D 2640 \; minimally-q - ualified # 🙆‍♀ E4.0 woman gesturing OK\n1F646 1F3FB 200D 2640 FE0F - \; fully-qualified # 🙆🏻‍♀️ E4.0 woman gest - uring OK: light skin tone\n1F646 1F3FB 200D 2640 \; m - inimally-qualified # 🙆🏻‍♀ E4.0 woman gesturing OK: light skin to - ne\n1F646 1F3FC 200D 2640 FE0F \; fully-qualified # - 🙆🏼‍♀️ E4.0 woman gesturing OK: medium-light skin tone\n1F646 1 - F3FC 200D 2640 \; minimally-qualified # 🙆🏼‍ - ♀ E4.0 woman gesturing OK: medium-light skin tone\n1F646 1F3FD 200D 2640 - FE0F \; fully-qualified # 🙆🏽‍♀️ E4.0 woma - n gesturing OK: medium skin tone\n1F646 1F3FD 200D 2640 - \; minimally-qualified # 🙆🏽‍♀ E4.0 woman gesturing OK: medium - skin tone\n1F646 1F3FE 200D 2640 FE0F \; fully-qualified - # 🙆🏾‍♀️ E4.0 woman gesturing OK: medium-dark skin tone\n1F - 646 1F3FE 200D 2640 \; minimally-qualified # 🙆🏾 - ‍♀ E4.0 woman gesturing OK: medium-dark skin tone\n1F646 1F3FF 200D 26 - 40 FE0F \; fully-qualified # 🙆🏿‍♀️ E4.0 wo - man gesturing OK: dark skin tone\n1F646 1F3FF 200D 2640 - \; minimally-qualified # 🙆🏿‍♀ E4.0 woman gesturing OK: dark s - kin tone\n1F481 \; fully-qualified - # 💁 E2.0 person tipping hand\n1F481 1F3FB - \; fully-qualified # 💁🏻 E2.0 person tipping hand: light skin - tone\n1F481 1F3FC \; fully-qualified # - 💁�� E2.0 person tipping hand: medium-light skin tone\n1F481 1F3FD - \; fully-qualified # 💁🏽 E2.0 pers - on tipping hand: medium skin tone\n1F481 1F3FE - \; fully-qualified # 💁🏾 E2.0 person tipping hand: medium-dar - k skin tone\n1F481 1F3FF \; fully-qualified - # 💁🏿 E2.0 person tipping hand: dark skin tone\n1F481 200D 2642 - FE0F \; fully-qualified # 💁‍♂️ E4.0 man - tipping hand\n1F481 200D 2642 \; minimally-qua - lified # 💁‍♂ E4.0 man tipping hand\n1F481 1F3FB 200D 2642 FE0F - \; fully-qualified # 💁🏻‍♂️ E4.0 man tipping ha - nd: light skin tone\n1F481 1F3FB 200D 2642 \; minimal - ly-qualified # 💁🏻‍♂ E4.0 man tipping hand: light skin tone\n1F48 - 1 1F3FC 200D 2642 FE0F \; fully-qualified # 💁🏼 - ‍♂️ E4.0 man tipping hand: medium-light skin tone\n1F481 1F3FC 200D - 2642 \; minimally-qualified # 💁🏼‍♂ E4.0 man - tipping hand: medium-light skin tone\n1F481 1F3FD 200D 2642 FE0F - \; fully-qualified # 💁🏽‍♂️ E4.0 man tipping hand: - medium skin tone\n1F481 1F3FD 200D 2642 \; minimally- - qualified # 💁🏽‍♂ E4.0 man tipping hand: medium skin tone\n1F481 - 1F3FE 200D 2642 FE0F \; fully-qualified # 💁🏾‍ - ♂️ E4.0 man tipping hand: medium-dark skin tone\n1F481 1F3FE 200D 2642 - \; minimally-qualified # 💁🏾‍♂ E4.0 man tip - ping hand: medium-dark skin tone\n1F481 1F3FF 200D 2642 FE0F - \; fully-qualified # 💁🏿‍♂️ E4.0 man tipping hand: dark - skin tone\n1F481 1F3FF 200D 2642 \; minimally-qualifi - ed # 💁🏿‍♂ E4.0 man tipping hand: dark skin tone\n1F481 200D 2640 - FE0F \; fully-qualified # 💁‍♀️ E4.0 wo - man tipping hand\n1F481 200D 2640 \; minimally- - qualified # 💁‍♀ E4.0 woman tipping hand\n1F481 1F3FB 200D 2640 FE0F - \; fully-qualified # 💁🏻‍♀️ E4.0 woman tip - ping hand: light skin tone\n1F481 1F3FB 200D 2640 \; - minimally-qualified # 💁🏻‍♀ E4.0 woman tipping hand: light skin t - one\n1F481 1F3FC 200D 2640 FE0F \; fully-qualified # - 💁🏼‍♀️ E4.0 woman tipping hand: medium-light skin tone\n1F481 1 - F3FC 200D 2640 \; minimally-qualified # 💁🏼‍ - ♀ E4.0 woman tipping hand: medium-light skin tone\n1F481 1F3FD 200D 2640 - FE0F \; fully-qualified # 💁🏽‍♀️ E4.0 woma - n tipping hand: medium skin tone\n1F481 1F3FD 200D 2640 - \; minimally-qualified # 💁🏽‍♀ E4.0 woman tipping hand: medium - skin tone\n1F481 1F3FE 200D 2640 FE0F \; fully-qualified - # 💁🏾‍♀️ E4.0 woman tipping hand: medium-dark skin tone\n1F - 481 1F3FE 200D 2640 \; minimally-qualified # 💁🏾 - ‍♀ E4.0 woman tipping hand: medium-dark skin tone\n1F481 1F3FF 200D 26 - 40 FE0F \; fully-qualified # 💁🏿‍♀️ E4.0 wo - man tipping hand: dark skin tone\n1F481 1F3FF 200D 2640 - \; minimally-qualified # 💁🏿‍♀ E4.0 woman tipping hand: dark s - kin tone\n1F64B \; fully-qualified - # 🙋 E2.0 person raising hand\n1F64B 1F3FB - \; fully-qualified # 🙋🏻 E2.0 person raising hand: light skin - tone\n1F64B 1F3FC \; fully-qualified # - 🙋🏼 E2.0 person raising hand: medium-light skin tone\n1F64B 1F3FD - \; fully-qualified # 🙋🏽 E2.0 person - raising hand: medium skin tone\n1F64B 1F3FE - \; fully-qualified # 🙋🏾 E2.0 person raising hand: medium-dark - skin tone\n1F64B 1F3FF \; fully-qualified - # 🙋🏿 E2.0 person raising hand: dark skin tone\n1F64B 200D 2642 FE - 0F \; fully-qualified # 🙋‍♂️ E4.0 man r - aising hand\n1F64B 200D 2642 \; minimally-quali - fied # 🙋‍♂ E4.0 man raising hand\n1F64B 1F3FB 200D 2642 FE0F - \; fully-qualified # 🙋🏻‍♂️ E4.0 man raising hand - : light skin tone\n1F64B 1F3FB 200D 2642 \; minimally - -qualified # 🙋🏻‍♂ E4.0 man raising hand: light skin tone\n1F64B - 1F3FC 200D 2642 FE0F \; fully-qualified # 🙋🏼‍ - ♂️ E4.0 man raising hand: medium-light skin tone\n1F64B 1F3FC 200D 264 - 2 \; minimally-qualified # 🙋🏼‍♂ E4.0 man ra - ising hand: medium-light skin tone\n1F64B 1F3FD 200D 2642 FE0F - \; fully-qualified # 🙋🏽‍♂️ E4.0 man raising hand: med - ium skin tone\n1F64B 1F3FD 200D 2642 \; minimally-qua - lified # 🙋��‍♂ E4.0 man raising hand: medium skin tone\n1F64B 1 - F3FE 200D 2642 FE0F \; fully-qualified # 🙋🏾‍ - ♂️ E4.0 man raising hand: medium-dark skin tone\n1F64B 1F3FE 200D 2642 - \; minimally-qualified # 🙋🏾‍♂ E4.0 man rai - sing hand: medium-dark skin tone\n1F64B 1F3FF 200D 2642 FE0F - \; fully-qualified # 🙋🏿‍♂️ E4.0 man raising hand: dark - skin tone\n1F64B 1F3FF 200D 2642 \; minimally-qualifi - ed # 🙋🏿‍♂ E4.0 man raising hand: dark skin tone\n1F64B 200D 2640 - FE0F \; fully-qualified # 🙋‍♀️ E4.0 wo - man raising hand\n1F64B 200D 2640 \; minimally- - qualified # 🙋‍♀ E4.0 woman raising hand\n1F64B 1F3FB 200D 2640 FE0F - \; fully-qualified # 🙋🏻‍♀️ E4.0 woman rai - sing hand: light skin tone\n1F64B 1F3FB 200D 2640 \; - minimally-qualified # 🙋🏻‍♀ E4.0 woman raising hand: light skin t - one\n1F64B 1F3FC 200D 2640 FE0F \; fully-qualified # - 🙋🏼‍♀️ E4.0 woman raising hand: medium-light skin tone\n1F64B 1 - F3FC 200D 2640 \; minimally-qualified # 🙋🏼‍ - ♀ E4.0 woman raising hand: medium-light skin tone\n1F64B 1F3FD 200D 2640 - FE0F \; fully-qualified # 🙋🏽‍♀️ E4.0 woma - n raising hand: medium skin tone\n1F64B 1F3FD 200D 2640 - \; minimally-qualified # 🙋🏽‍♀ E4.0 woman raising hand: medium - skin tone\n1F64B 1F3FE 200D 2640 FE0F \; fully-qualified - # 🙋🏾‍♀️ E4.0 woman raising hand: medium-dark skin tone\n1F - 64B 1F3FE 200D 2640 \; minimally-qualified # 🙋🏾 - ‍♀ E4.0 woman raising hand: medium-dark skin tone\n1F64B 1F3FF 200D 26 - 40 FE0F \; fully-qualified # 🙋🏿‍♀️ E4.0 wo - man raising hand: dark skin tone\n1F64B 1F3FF 200D 2640 - \; minimally-qualified # 🙋🏿‍♀ E4.0 woman raising hand: dark s - kin tone\n1F9CF \; fully-qualified - # 🧏 E12.1 deaf person\n1F9CF 1F3FB \; - fully-qualified # 🧏🏻 E12.1 deaf person: light skin tone\n1F9CF 1 - F3FC \; fully-qualified # 🧏🏼 E12. - 1 deaf person: medium-light skin tone\n1F9CF 1F3FD - \; fully-qualified # 🧏🏽 E12.1 deaf person: medium skin t - one\n1F9CF 1F3FE \; fully-qualified # - 🧏🏾 E12.1 deaf person: medium-dark skin tone\n1F9CF 1F3FF - \; fully-qualified # 🧏🏿 E12.1 deaf person: d - ark skin tone\n1F9CF 200D 2642 FE0F \; fully-qualifi - ed # 🧏‍♂️ E12.1 deaf man\n1F9CF 200D 2642 - \; minimally-qualified # 🧏‍♂ E12.1 deaf man\n1F9CF 1F3FB 20 - 0D 2642 FE0F \; fully-qualified # 🧏🏻‍♂️ E1 - 2.1 deaf man: light skin tone\n1F9CF 1F3FB 200D 2642 - \; minimally-qualified # 🧏🏻‍♂ E12.1 deaf man: light skin tone\n1 - F9CF 1F3FC 200D 2642 FE0F \; fully-qualified # 🧏 - 🏼‍♂️ E12.1 deaf man: medium-light skin tone\n1F9CF 1F3FC 200D 264 - 2 \; minimally-qualified # 🧏🏼‍♂ E12.1 deaf - man: medium-light skin tone\n1F9CF 1F3FD 200D 2642 FE0F \; - fully-qualified # 🧏🏽‍♂️ E12.1 deaf man: medium skin tone\ - n1F9CF 1F3FD 200D 2642 \; minimally-qualified # 🧏 - 🏽‍♂ E12.1 deaf man: medium skin tone\n1F9CF 1F3FE 200D 2642 FE0F - \; fully-qualified # 🧏🏾‍♂️ E12.1 deaf man: m - edium-dark skin tone\n1F9CF 1F3FE 200D 2642 \; minima - lly-qualified # 🧏🏾‍♂ E12.1 deaf man: medium-dark skin tone\n1F9C - F 1F3FF 200D 2642 FE0F \; fully-qualified # 🧏🏿 - ‍♂️ E12.1 deaf man: dark skin tone\n1F9CF 1F3FF 200D 2642 - \; minimally-qualified # 🧏🏿‍♂ E12.1 deaf man: dark sk - in tone\n1F9CF 200D 2640 FE0F \; fully-qualified - # 🧏‍♀️ E12.1 deaf woman\n1F9CF 200D 2640 - \; minimally-qualified # 🧏‍♀ E12.1 deaf woman\n1F9CF 1F3FB 200D - 2640 FE0F \; fully-qualified # 🧏🏻‍♀️ E12. - 1 deaf woman: light skin tone\n1F9CF 1F3FB 200D 2640 - \; minimally-qualified # 🧏🏻‍♀ E12.1 deaf woman: light skin tone\ - n1F9CF 1F3FC 200D 2640 FE0F \; fully-qualified # 🧏 - 🏼‍♀️ E12.1 deaf woman: medium-light skin tone\n1F9CF 1F3FC 200D 2 - 640 \; minimally-qualified # 🧏🏼‍♀ E12.1 dea - f woman: medium-light skin tone\n1F9CF 1F3FD 200D 2640 FE0F - \; fully-qualified # 🧏🏽‍♀️ E12.1 deaf woman: medium skin - tone\n1F9CF 1F3FD 200D 2640 \; minimally-qualified # - 🧏🏽‍♀ E12.1 deaf woman: medium skin tone\n1F9CF 1F3FE 200D 2640 - FE0F \; fully-qualified # 🧏🏾‍♀️ E12.1 deaf - woman: medium-dark skin tone\n1F9CF 1F3FE 200D 2640 - \; minimally-qualified # 🧏🏾‍♀ E12.1 deaf woman: medium-dark skin - tone\n1F9CF 1F3FF 200D 2640 FE0F \; fully-qualified # - 🧏🏿‍♀️ E12.1 deaf woman: dark skin tone\n1F9CF 1F3FF 200D 2640 - \; minimally-qualified # 🧏🏿‍♀ E12.1 deaf w - oman: dark skin tone\n1F647 \; fully- - qualified # 🙇 E2.0 person bowing\n1F647 1F3FB - \; fully-qualified # 🙇🏻 E2.0 person bowing: light skin - tone\n1F647 1F3FC \; fully-qualified # - 🙇🏼 E2.0 person bowing: medium-light skin tone\n1F647 1F3FD - \; fully-qualified # 🙇🏽 E2.0 person bowin - g: medium skin tone\n1F647 1F3FE \; fully-q - ualified # 🙇🏾 E2.0 person bowing: medium-dark skin tone\n1F647 1 - F3FF \; fully-qualified # 🙇🏿 E2.0 - person bowing: dark skin tone\n1F647 200D 2642 FE0F - \; fully-qualified # 🙇‍♂️ E4.0 man bowing\n1F647 200D 2642 - \; minimally-qualified # 🙇‍♂ E4.0 man bow - ing\n1F647 1F3FB 200D 2642 FE0F \; fully-qualified # - 🙇🏻‍♂️ E4.0 man bowing: light skin tone\n1F647 1F3FB 200D 2642 - \; minimally-qualified # 🙇🏻‍♂ E4.0 man bowi - ng: light skin tone\n1F647 1F3FC 200D 2642 FE0F \; fully-q - ualified # 🙇🏼‍♂️ E4.0 man bowing: medium-light skin tone\n - 1F647 1F3FC 200D 2642 \; minimally-qualified # 🙇 - 🏼‍♂ E4.0 man bowing: medium-light skin tone\n1F647 1F3FD 200D 2642 - FE0F \; fully-qualified # 🙇��‍♂️ E4.0 man - bowing: medium skin tone\n1F647 1F3FD 200D 2642 \; m - inimally-qualified # 🙇🏽‍♂ E4.0 man bowing: medium skin tone\n1F6 - 47 1F3FE 200D 2642 FE0F \; fully-qualified # 🙇🏾 - ‍♂️ E4.0 man bowing: medium-dark skin tone\n1F647 1F3FE 200D 2642 - \; minimally-qualified # 🙇🏾‍♂ E4.0 man bowing - : medium-dark skin tone\n1F647 1F3FF 200D 2642 FE0F \; ful - ly-qualified # ��🏿‍♂️ E4.0 man bowing: dark skin tone\n1F - 647 1F3FF 200D 2642 \; minimally-qualified # 🙇🏿 - ‍♂ E4.0 man bowing: dark skin tone\n1F647 200D 2640 FE0F - \; fully-qualified # 🙇‍♀️ E4.0 woman bowing\n1F647 2 - 00D 2640 \; minimally-qualified # 🙇‍♀ E4 - .0 woman bowing\n1F647 1F3FB 200D 2640 FE0F \; fully-quali - fied # 🙇🏻‍♀️ E4.0 woman bowing: light skin tone\n1F647 1F3 - FB 200D 2640 \; minimally-qualified # 🙇🏻‍♀ - E4.0 woman bowing: light skin tone\n1F647 1F3FC 200D 2640 FE0F - \; fully-qualified # 🙇🏼‍♀️ E4.0 woman bowing: medium- - light skin tone\n1F647 1F3FC 200D 2640 \; minimally-q - ualified # 🙇🏼‍♀ E4.0 woman bowing: medium-light skin tone\n1F647 - 1F3FD 200D 2640 FE0F \; fully-qualified # 🙇🏽‍ - ♀️ E4.0 woman bowing: medium skin tone\n1F647 1F3FD 200D 2640 - \; minimally-qualified # ��🏽‍♀ E4.0 woman bowing: - medium skin tone\n1F647 1F3FE 200D 2640 FE0F \; fully-qual - ified # 🙇🏾‍♀️ E4.0 woman bowing: medium-dark skin tone\n1F - 647 1F3FE 200D 2640 \; minimally-qualified # 🙇🏾 - ‍♀ E4.0 woman bowing: medium-dark skin tone\n1F647 1F3FF 200D 2640 FE0 - F \; fully-qualified # 🙇🏿‍♀️ E4.0 woman bo - wing: dark skin tone\n1F647 1F3FF 200D 2640 \; minima - lly-qualified # 🙇🏿‍♀ E4.0 woman bowing: dark skin tone\n1F926 - \; fully-qualified # 🤦 E4.0 pers - on facepalming\n1F926 1F3FB \; fully-qualif - ied # 🤦🏻 E4.0 person facepalming: light skin tone\n1F926 1F3FC - \; fully-qualified # 🤦🏼 E4.0 perso - n facepalming: medium-light skin tone\n1F926 1F3FD - \; fully-qualified # 🤦🏽 E4.0 person facepalming: medium - skin tone\n1F926 1F3FE \; fully-qualified - # 🤦🏾 E4.0 person facepalming: medium-dark skin tone\n1F926 1F3FF - \; fully-qualified # 🤦🏿 E4.0 pers - on facepalming: dark skin tone\n1F926 200D 2642 FE0F - \; fully-qualified # 🤦‍♂️ E4.0 man facepalming\n1F926 200D 2 - 642 \; minimally-qualified # 🤦‍♂ E4.0 ma - n facepalming\n1F926 1F3FB 200D 2642 FE0F \; fully-qualifi - ed # 🤦🏻‍♂️ E4.0 man facepalming: light skin tone\n1F926 1F - 3FB 200D 2642 \; minimally-qualified # 🤦🏻‍♂ - E4.0 man facepalming: light skin tone\n1F926 1F3FC 200D 2642 FE0F - \; fully-qualified # 🤦🏼‍♂️ E4.0 man facepalming: - medium-light skin tone\n1F926 1F3FC 200D 2642 \; mini - mally-qualified # 🤦🏼‍♂ E4.0 man facepalming: medium-light skin t - one\n1F926 1F3FD 200D 2642 FE0F \; fully-qualified # - 🤦🏽‍♂️ E4.0 man facepalming: medium skin tone\n1F926 1F3FD 200D - 2642 \; minimally-qualified # 🤦🏽‍♂ E4.0 ma - n facepalming: medium skin tone\n1F926 1F3FE 200D 2642 FE0F - \; fully-qualified # 🤦🏾‍♂️ E4.0 man facepalming: medium- - dark skin tone\n1F926 1F3FE 200D 2642 \; minimally-qu - alified # 🤦🏾‍♂ E4.0 man facepalming: medium-dark skin tone\n1F92 - 6 1F3FF 200D 2642 FE0F \; fully-qualified # 🤦🏿 - ‍♂️ E4.0 man facepalming: dark skin tone\n1F926 1F3FF 200D 2642 - \; minimally-qualified # 🤦🏿‍♂ E4.0 man facepalm - ing: dark skin tone\n1F926 200D 2640 FE0F \; fully-q - ualified # 🤦‍♀️ E4.0 woman facepalming\n1F926 200D 2640 - \; minimally-qualified # 🤦‍♀ E4.0 woman facep - alming\n1F926 1F3FB 200D 2640 FE0F \; fully-qualified - # 🤦🏻‍♀️ E4.0 woman facepalming: light skin tone\n1F926 1F3FB 2 - 00D 2640 \; minimally-qualified # 🤦🏻‍♀ E4.0 - woman facepalming: light skin tone\n1F926 1F3FC 200D 2640 FE0F - \; fully-qualified # 🤦🏼‍♀️ E4.0 woman facepalming: m - edium-light skin tone\n1F926 1F3FC 200D 2640 \; minim - ally-qualified # 🤦🏼‍♀ E4.0 woman facepalming: medium-light skin - tone\n1F926 1F3FD 200D 2640 FE0F \; fully-qualified # - 🤦🏽‍♀️ E4.0 woman facepalming: medium skin tone\n1F926 1F3FD 20 - 0D 2640 \; minimally-qualified # 🤦🏽‍♀ E4.0 - woman facepalming: medium skin tone\n1F926 1F3FE 200D 2640 FE0F - \; fully-qualified # 🤦🏾‍♀️ E4.0 woman facepalming: m - edium-dark skin tone\n1F926 1F3FE 200D 2640 \; minima - lly-qualified # 🤦🏾‍♀ E4.0 woman facepalming: medium-dark skin to - ne\n1F926 1F3FF 200D 2640 FE0F \; fully-qualified # - 🤦🏿‍♀️ E4.0 woman facepalming: dark skin tone\n1F926 1F3FF 200D - 2640 \; minimally-qualified # 🤦🏿‍♀ E4.0 wo - man facepalming: dark skin tone\n1F937 - \; fully-qualified # 🤷 E4.0 person shrugging\n1F937 1F3FB - \; fully-qualified # 🤷🏻 E4.0 person shru - gging: light skin tone\n1F937 1F3FC \; full - y-qualified # 🤷🏼 E4.0 person shrugging: medium-light skin tone\n - 1F937 1F3FD \; fully-qualified # 🤷 - 🏽 E4.0 person shrugging: medium skin tone\n1F937 1F3FE - \; fully-qualified # 🤷🏾 E4.0 person shrugging: me - dium-dark skin tone\n1F937 1F3FF \; fully-q - ualified # 🤷🏿 E4.0 person shrugging: dark skin tone\n1F937 200D - 2642 FE0F \; fully-qualified # 🤷‍♂️ E4. - 0 man shrugging\n1F937 200D 2642 \; minimally-q - ualified # 🤷‍♂ E4.0 man shrugging\n1F937 1F3FB 200D 2642 FE0F - \; fully-qualified # 🤷🏻‍♂️ E4.0 man shrugging: - light skin tone\n1F937 1F3FB 200D 2642 \; minimally-q - ualified # 🤷🏻‍♂ E4.0 man shrugging: light skin tone\n1F937 1F3FC - 200D 2642 FE0F \; fully-qualified # 🤷🏼‍♂️ - E4.0 man shrugging: medium-light skin tone\n1F937 1F3FC 200D 2642 - \; minimally-qualified # 🤷🏼‍♂ E4.0 man shrugging: - medium-light skin tone\n1F937 1F3FD 200D 2642 FE0F \; full - y-qualified # 🤷��‍♂️ E4.0 man shrugging: medium skin tone - \n1F937 1F3FD 200D 2642 \; minimally-qualified # 🤷 - 🏽‍♂ E4.0 man shrugging: medium skin tone\n1F937 1F3FE 200D 2642 FE0 - F \; fully-qualified # ��🏾‍♂️ E4.0 man sh - rugging: medium-dark skin tone\n1F937 1F3FE 200D 2642 - \; minimally-qualified # 🤷🏾‍♂ E4.0 man shrugging: medium-dark s - kin tone\n1F937 1F3FF 200D 2642 FE0F \; fully-qualified - # 🤷🏿‍♂️ E4.0 man shrugging: dark skin tone\n1F937 1F3FF 200D - 2642 \; minimally-qualified # 🤷🏿‍♂ E4.0 ma - n shrugging: dark skin tone\n1F937 200D 2640 FE0F \; - fully-qualified # 🤷‍♀️ E4.0 woman shrugging\n1F937 200D 2640 - \; minimally-qualified # 🤷‍♀ E4.0 woman - shrugging\n1F937 1F3FB 200D 2640 FE0F \; fully-qualified - # 🤷🏻‍♀️ E4.0 woman shrugging: light skin tone\n1F937 1F3FB - 200D 2640 \; minimally-qualified # 🤷🏻‍♀ E4 - .0 woman shrugging: light skin tone\n1F937 1F3FC 200D 2640 FE0F - \; fully-qualified # 🤷🏼‍♀️ E4.0 woman shrugging: med - ium-light skin tone\n1F937 1F3FC 200D 2640 \; minimal - ly-qualified # 🤷🏼‍♀ E4.0 woman shrugging: medium-light skin tone - \n1F937 1F3FD 200D 2640 FE0F \; fully-qualified # 🤷 - ��‍♀️ E4.0 woman shrugging: medium skin tone\n1F937 1F3FD 200D 2 - 640 \; minimally-qualified # 🤷🏽‍♀ E4.0 woma - n shrugging: medium skin tone\n1F937 1F3FE 200D 2640 FE0F - \; fully-qualified # 🤷🏾‍♀️ E4.0 woman shrugging: medium-da - rk skin tone\n1F937 1F3FE 200D 2640 \; minimally-qual - ified # 🤷🏾‍♀ E4.0 woman shrugging: medium-dark skin tone\n1F937 - 1F3FF 200D 2640 FE0F \; fully-qualified # 🤷🏿‍ - ♀️ E4.0 woman shrugging: dark skin tone\n1F937 1F3FF 200D 2640 - \; minimally-qualified # 🤷🏿‍♀ E4.0 woman shrugging - : dark skin tone\n\n# subgroup: person-role\n1F9D1 200D 2695 FE0F - \; fully-qualified # 🧑‍⚕️ E12.1 health worker\n - 1F9D1 200D 2695 \; minimally-qualified # 🧑 - ‍⚕ E12.1 health worker\n1F9D1 1F3FB 200D 2695 FE0F \; - fully-qualified # 🧑🏻‍⚕️ E12.1 health worker: light skin to - ne\n1F9D1 1F3FB 200D 2695 \; minimally-qualified # - 🧑🏻‍⚕ E12.1 health worker: light skin tone\n1F9D1 1F3FC 200D 2695 - FE0F \; fully-qualified # 🧑🏼‍⚕️ E12.1 hea - lth worker: medium-light skin tone\n1F9D1 1F3FC 200D 2695 - \; minimally-qualified # 🧑🏼‍⚕ E12.1 health worker: medium-l - ight skin tone\n1F9D1 1F3FD 200D 2695 FE0F \; fully-qualif - ied # 🧑🏽‍⚕️ E12.1 health worker: medium skin tone\n1F9D1 1 - F3FD 200D 2695 \; minimally-qualified # 🧑🏽‍ - ⚕ E12.1 health worker: medium skin tone\n1F9D1 1F3FE 200D 2695 FE0F - \; fully-qualified # 🧑🏾‍⚕️ E12.1 health worker - : medium-dark skin tone\n1F9D1 1F3FE 200D 2695 \; min - imally-qualified # 🧑🏾‍⚕ E12.1 health worker: medium-dark skin to - ne\n1F9D1 1F3FF 200D 2695 FE0F \; fully-qualified # - 🧑🏿‍⚕️ E12.1 health worker: dark skin tone\n1F9D1 1F3FF 200D 26 - 95 \; minimally-qualified # 🧑🏿‍⚕ E12.1 heal - th worker: dark skin tone\n1F468 200D 2695 FE0F \; f - ully-qualified # 👨‍⚕️ E4.0 man health worker\n1F468 200D 2695 - \; minimally-qualified # 👨‍⚕ E4.0 man h - ealth worker\n1F468 1F3FB 200D 2695 FE0F \; fully-qualifie - d # 👨🏻‍⚕️ E4.0 man health worker: light skin tone\n1F468 1 - F3FB 200D 2695 \; minimally-qualified # 👨🏻‍ - ⚕ E4.0 man health worker: light skin tone\n1F468 1F3FC 200D 2695 FE0F - \; fully-qualified # 👨🏼‍⚕️ E4.0 man health w - orker: medium-light skin tone\n1F468 1F3FC 200D 2695 - \; minimally-qualified # 👨🏼‍⚕ E4.0 man health worker: medium-lig - ht skin tone\n1F468 1F3FD 200D 2695 FE0F \; fully-qualifie - d # 👨🏽‍⚕️ E4.0 man health worker: medium skin tone\n1F468 - 1F3FD 200D 2695 \; minimally-qualified # 👨🏽‍ - ⚕ E4.0 man health worker: medium skin tone\n1F468 1F3FE 200D 2695 FE0F - \; fully-qualified # 👨🏾‍⚕️ E4.0 man health - worker: medium-dark skin tone\n1F468 1F3FE 200D 2695 - \; minimally-qualified # 👨🏾‍⚕ E4.0 man health worker: medium-dar - k skin tone\n1F468 1F3FF 200D 2695 FE0F \; fully-qualified - # 👨🏿‍⚕️ E4.0 man health worker: dark skin tone\n1F468 1F3 - FF 200D 2695 \; minimally-qualified # 👨🏿‍⚕ - E4.0 man health worker: dark skin tone\n1F469 200D 2695 FE0F - \; fully-qualified # 👩‍⚕️ E4.0 woman health worker\n - 1F469 200D 2695 \; minimally-qualified # 👩 - ‍⚕ E4.0 woman health worker\n1F469 1F3FB 200D 2695 FE0F - \; fully-qualified # 👩🏻‍⚕️ E4.0 woman health worker: lig - ht skin tone\n1F469 1F3FB 200D 2695 \; minimally-qual - ified # 👩🏻‍⚕ E4.0 woman health worker: light skin tone\n1F469 1F - 3FC 200D 2695 FE0F \; fully-qualified # 👩🏼‍⚕ - ️ E4.0 woman health worker: medium-light skin tone\n1F469 1F3FC 200D 269 - 5 \; minimally-qualified # 👩🏼‍⚕ E4.0 woman - health worker: medium-light skin tone\n1F469 1F3FD 200D 2695 FE0F - \; fully-qualified # 👩🏽‍⚕️ E4.0 woman health worke - r: medium skin tone\n1F469 1F3FD 200D 2695 \; minimal - ly-qualified # 👩🏽‍⚕ E4.0 woman health worker: medium skin tone\n - 1F469 1F3FE 200D 2695 FE0F \; fully-qualified # 👩 - 🏾‍⚕️ E4.0 woman health worker: medium-dark skin tone\n1F469 1F3FE - 200D 2695 \; minimally-qualified # 👩🏾‍⚕ E4 - .0 woman health worker: medium-dark skin tone\n1F469 1F3FF 200D 2695 FE0F - \; fully-qualified # 👩🏿‍⚕️ E4.0 woman heal - th worker: dark skin tone\n1F469 1F3FF 200D 2695 \; m - inimally-qualified # 👩🏿‍⚕ E4.0 woman health worker: dark skin to - ne\n1F9D1 200D 1F393 \; fully-qualified # - 🧑‍🎓 E12.1 student\n1F9D1 1F3FB 200D 1F393 \; f - ully-qualified # 🧑🏻‍🎓 E12.1 student: light skin tone\n1F9D1 - 1F3FC 200D 1F393 \; fully-qualified # 🧑🏼‍ - 🎓 E12.1 student: medium-light skin tone\n1F9D1 1F3FD 200D 1F393 - \; fully-qualified # 🧑🏽‍🎓 E12.1 student: mediu - m skin tone\n1F9D1 1F3FE 200D 1F393 \; fully-qualified - # 🧑🏾‍🎓 E12.1 student: medium-dark skin tone\n1F9D1 1F3FF 2 - 00D 1F393 \; fully-qualified # 🧑🏿‍🎓 E12 - .1 student: dark skin tone\n1F468 200D 1F393 \; - fully-qualified # 👨‍🎓 E4.0 man student\n1F468 1F3FB 200D 1F393 - \; fully-qualified # 👨🏻‍🎓 E4.0 man stu - dent: light skin tone\n1F468 1F3FC 200D 1F393 \; fully - -qualified # 👨🏼‍🎓 E4.0 man student: medium-light skin tone\ - n1F468 1F3FD 200D 1F393 \; fully-qualified # 👨 - 🏽‍🎓 E4.0 man student: medium skin tone\n1F468 1F3FE 200D 1F393 - \; fully-qualified # 👨🏾‍🎓 E4.0 man student - : medium-dark skin tone\n1F468 1F3FF 200D 1F393 \; ful - ly-qualified # 👨🏿‍🎓 E4.0 man student: dark skin tone\n1F469 - 200D 1F393 \; fully-qualified # 👩‍🎓 - E4.0 woman student\n1F469 1F3FB 200D 1F393 \; fully-q - ualified # 👩🏻‍🎓 E4.0 woman student: light skin tone\n1F469 - 1F3FC 200D 1F393 \; fully-qualified # 👩🏼‍ - 🎓 E4.0 woman student: medium-light skin tone\n1F469 1F3FD 200D 1F393 - \; fully-qualified # 👩🏽‍🎓 E4.0 woman stud - ent: medium skin tone\n1F469 1F3FE 200D 1F393 \; fully - -qualified # 👩🏾‍🎓 E4.0 woman student: medium-dark skin tone - \n1F469 1F3FF 200D 1F393 \; fully-qualified # 👩 - 🏿‍🎓 E4.0 woman student: dark skin tone\n1F9D1 200D 1F3EB - \; fully-qualified # 🧑‍🏫 E12.1 teacher\n1F9D1 - 1F3FB 200D 1F3EB \; fully-qualified # 🧑🏻‍ - 🏫 E12.1 teacher: light skin tone\n1F9D1 1F3FC 200D 1F3EB - \; fully-qualified # 🧑🏼‍🏫 E12.1 teacher: medium-light - skin tone\n1F9D1 1F3FD 200D 1F3EB \; fully-qualified - # 🧑🏽‍🏫 E12.1 teacher: medium skin tone\n1F9D1 1F3FE 200D 1F - 3EB \; fully-qualified # 🧑🏾‍🏫 E12.1 tea - cher: medium-dark skin tone\n1F9D1 1F3FF 200D 1F3EB \; - fully-qualified # 🧑🏿‍🏫 E12.1 teacher: dark skin tone\n1F46 - 8 200D 1F3EB \; fully-qualified # 👨‍ - 🏫 E4.0 man teacher\n1F468 1F3FB 200D 1F3EB \; fully - -qualified # 👨🏻‍🏫 E4.0 man teacher: light skin tone\n1F468 - 1F3FC 200D 1F3EB \; fully-qualified # 👨🏼‍ - 🏫 E4.0 man teacher: medium-light skin tone\n1F468 1F3FD 200D 1F3EB - \; fully-qualified # 👨🏽‍🏫 E4.0 man teacher: - medium skin tone\n1F468 1F3FE 200D 1F3EB \; fully-qua - lified # 👨🏾‍🏫 E4.0 man teacher: medium-dark skin tone\n1F46 - 8 1F3FF 200D 1F3EB \; fully-qualified # 👨🏿 - ‍🏫 E4.0 man teacher: dark skin tone\n1F469 200D 1F3EB - \; fully-qualified # 👩‍🏫 E4.0 woman teacher\n1F469 - 1F3FB 200D 1F3EB \; fully-qualified # 👩🏻‍ - 🏫 E4.0 woman teacher: light skin tone\n1F469 1F3FC 200D 1F3EB - \; fully-qualified # 👩🏼‍🏫 E4.0 woman teacher: me - dium-light skin tone\n1F469 1F3FD 200D 1F3EB \; fully- - qualified # 👩��‍🏫 E4.0 woman teacher: medium skin tone\n1F - 469 1F3FE 200D 1F3EB \; fully-qualified # 👩🏾 - ‍🏫 E4.0 woman teacher: medium-dark skin tone\n1F469 1F3FF 200D 1F3EB - \; fully-qualified # 👩🏿‍🏫 E4.0 woman te - acher: dark skin tone\n1F9D1 200D 2696 FE0F \; fully - -qualified # 🧑‍⚖️ E12.1 judge\n1F9D1 200D 2696 - \; minimally-qualified # 🧑‍⚖ E12.1 judge\n1F9D1 1F3FB - 200D 2696 FE0F \; fully-qualified # 🧑🏻‍⚖️ - E12.1 judge: light skin tone\n1F9D1 1F3FB 200D 2696 \ - ; minimally-qualified # 🧑🏻‍⚖ E12.1 judge: light skin tone\n1F9D1 - 1F3FC 200D 2696 FE0F \; fully-qualified # 🧑🏼‍ - ⚖️ E12.1 judge: medium-light skin tone\n1F9D1 1F3FC 200D 2696 - \; minimally-qualified # 🧑🏼‍⚖ E12.1 judge: medium-l - ight skin tone\n1F9D1 1F3FD 200D 2696 FE0F \; fully-qualif - ied # 🧑🏽‍⚖️ E12.1 judge: medium skin tone\n1F9D1 1F3FD 200 - D 2696 \; minimally-qualified # 🧑🏽‍⚖ E12.1 - judge: medium skin tone\n1F9D1 1F3FE 200D 2696 FE0F \; ful - ly-qualified # 🧑🏾‍⚖️ E12.1 judge: medium-dark skin tone\n1 - F9D1 1F3FE 200D 2696 \; minimally-qualified # 🧑 - 🏾‍⚖ E12.1 judge: medium-dark skin tone\n1F9D1 1F3FF 200D 2696 FE0F - \; fully-qualified # 🧑🏿‍⚖️ E12.1 judge: da - rk skin tone\n1F9D1 1F3FF 200D 2696 \; minimally-qual - ified # 🧑🏿‍⚖ E12.1 judge: dark skin tone\n1F468 200D 2696 FE0F - \; fully-qualified # 👨‍⚖️ E4.0 man judge - \n1F468 200D 2696 \; minimally-qualified # 👨 - ‍⚖ E4.0 man judge\n1F468 1F3FB 200D 2696 FE0F \; fully - -qualified # 👨🏻‍⚖️ E4.0 man judge: light skin tone\n1F468 - 1F3FB 200D 2696 \; minimally-qualified # 👨🏻‍ - ⚖ E4.0 man judge: light skin tone\n1F468 1F3FC 200D 2696 FE0F - \; fully-qualified # 👨🏼‍⚖️ E4.0 man judge: medium-li - ght skin tone\n1F468 1F3FC 200D 2696 \; minimally-qua - lified # 👨🏼‍⚖ E4.0 man judge: medium-light skin tone\n1F468 1F3F - D 200D 2696 FE0F \; fully-qualified # 👨🏽‍⚖ - ️ E4.0 man judge: medium skin tone\n1F468 1F3FD 200D 2696 - \; minimally-qualified # 👨🏽‍⚖ E4.0 man judge: medium skin - tone\n1F468 1F3FE 200D 2696 FE0F \; fully-qualified # - 👨🏾‍⚖️ E4.0 man judge: medium-dark skin tone\n1F468 1F3FE 200D - 2696 \; minimally-qualified # 👨🏾‍⚖ E4.0 ma - n judge: medium-dark skin tone\n1F468 1F3FF 200D 2696 FE0F - \; fully-qualified # 👨🏿‍⚖️ E4.0 man judge: dark skin tone - \n1F468 1F3FF 200D 2696 \; minimally-qualified # 👨 - 🏿‍⚖ E4.0 man judge: dark skin tone\n1F469 200D 2696 FE0F - \; fully-qualified # 👩‍⚖️ E4.0 woman judge\n1F469 - 200D 2696 \; minimally-qualified # 👩‍⚖ - E4.0 woman judge\n1F469 1F3FB 200D 2696 FE0F \; fully-qual - ified # 👩🏻‍⚖️ E4.0 woman judge: light skin tone\n1F469 1F3 - FB 200D 2696 \; minimally-qualified # 👩🏻‍⚖ - E4.0 woman judge: light skin tone\n1F469 1F3FC 200D 2696 FE0F - \; fully-qualified # 👩🏼‍⚖️ E4.0 woman judge: medium-li - ght skin tone\n1F469 1F3FC 200D 2696 \; minimally-qua - lified # 👩🏼‍⚖ E4.0 woman judge: medium-light skin tone\n1F469 1F - 3FD 200D 2696 FE0F \; fully-qualified # 👩🏽‍⚖ - ️ E4.0 woman judge: medium skin tone\n1F469 1F3FD 200D 2696 - \; minimally-qualified # 👩🏽‍⚖ E4.0 woman judge: medium - skin tone\n1F469 1F3FE 200D 2696 FE0F \; fully-qualified - # 👩🏾‍⚖️ E4.0 woman judge: medium-dark skin tone\n1F469 1F3F - E 200D 2696 \; minimally-qualified # 👩🏾‍⚖ E - 4.0 woman judge: medium-dark skin tone\n1F469 1F3FF 200D 2696 FE0F - \; fully-qualified # 👩🏿‍⚖️ E4.0 woman judge: dark - skin tone\n1F469 1F3FF 200D 2696 \; minimally-qualif - ied # 👩🏿‍⚖ E4.0 woman judge: dark skin tone\n1F9D1 200D 1F33E - \; fully-qualified # 🧑‍🌾 E12.1 farmer\ - n1F9D1 1F3FB 200D 1F33E \; fully-qualified # 🧑 - 🏻‍🌾 E12.1 farmer: light skin tone\n1F9D1 1F3FC 200D 1F33E - \; fully-qualified # 🧑🏼‍🌾 E12.1 farmer: medium- - light skin tone\n1F9D1 1F3FD 200D 1F33E \; fully-quali - fied # 🧑🏽‍🌾 E12.1 farmer: medium skin tone\n1F9D1 1F3FE 200 - D 1F33E \; fully-qualified # 🧑🏾‍🌾 E12.1 - farmer: medium-dark skin tone\n1F9D1 1F3FF 200D 1F33E - \; fully-qualified # 🧑🏿‍🌾 E12.1 farmer: dark skin tone\n1F - 468 200D 1F33E \; fully-qualified # 👨‍ - 🌾 E4.0 man farmer\n1F468 1F3FB 200D 1F33E \; fully- - qualified # 👨🏻‍🌾 E4.0 man farmer: light skin tone\n1F468 1F - 3FC 200D 1F33E \; fully-qualified # 👨🏼‍ - 🌾 E4.0 man farmer: medium-light skin tone\n1F468 1F3FD 200D 1F33E - \; fully-qualified # 👨🏽‍🌾 E4.0 man farmer: m - edium skin tone\n1F468 1F3FE 200D 1F33E \; fully-quali - fied # 👨🏾‍🌾 E4.0 man farmer: medium-dark skin tone\n1F468 1 - F3FF 200D 1F33E \; fully-qualified # 👨🏿‍ - 🌾 E4.0 man farmer: dark skin tone\n1F469 200D 1F33E - \; fully-qualified # 👩‍🌾 E4.0 woman farmer\n1F469 1F3FB - 200D 1F33E \; fully-qualified # 👩🏻‍🌾 E - 4.0 woman farmer: light skin tone\n1F469 1F3FC 200D 1F33E - \; fully-qualified # 👩🏼‍🌾 E4.0 woman farmer: medium-lig - ht skin tone\n1F469 1F3FD 200D 1F33E \; fully-qualifie - d # 👩🏽‍🌾 E4.0 woman farmer: medium skin tone\n1F469 1F3FE 2 - 00D 1F33E \; fully-qualified # 👩🏾‍🌾 E4. - 0 woman farmer: medium-dark skin tone\n1F469 1F3FF 200D 1F33E - \; fully-qualified # 👩🏿‍🌾 E4.0 woman farmer: dark s - kin tone\n1F9D1 200D 1F373 \; fully-qualified - # 🧑‍🍳 E12.1 cook\n1F9D1 1F3FB 200D 1F373 \; - fully-qualified # 🧑🏻‍🍳 E12.1 cook: light skin tone\n1F9D1 1 - F3FC 200D 1F373 \; fully-qualified # 🧑🏼‍ - 🍳 E12.1 cook: medium-light skin tone\n1F9D1 1F3FD 200D 1F373 - \; fully-qualified # 🧑🏽‍🍳 E12.1 cook: medium skin - tone\n1F9D1 1F3FE 200D 1F373 \; fully-qualified # - 🧑🏾‍🍳 E12.1 cook: medium-dark skin tone\n1F9D1 1F3FF 200D 1F373 - \; fully-qualified # 🧑🏿‍🍳 E12.1 cook: - dark skin tone\n1F468 200D 1F373 \; fully-qualif - ied # 👨‍🍳 E4.0 man cook\n1F468 1F3FB 200D 1F373 - \; fully-qualified # 👨🏻‍🍳 E4.0 man cook: light skin t - one\n1F468 1F3FC 200D 1F373 \; fully-qualified # - 👨🏼‍🍳 E4.0 man cook: medium-light skin tone\n1F468 1F3FD 200D 1F - 373 \; fully-qualified # 👨🏽‍🍳 E4.0 man - cook: medium skin tone\n1F468 1F3FE 200D 1F373 \; full - y-qualified # 👨🏾‍🍳 E4.0 man cook: medium-dark skin tone\n1F - 468 1F3FF 200D 1F373 \; fully-qualified # 👨🏿 - ‍🍳 E4.0 man cook: dark skin tone\n1F469 200D 1F373 - \; fully-qualified # 👩‍🍳 E4.0 woman cook\n1F469 1F3FB - 200D 1F373 \; fully-qualified # 👩🏻‍🍳 E4 - .0 woman cook: light skin tone\n1F469 1F3FC 200D 1F373 - \; fully-qualified # 👩🏼‍🍳 E4.0 woman cook: medium-light sk - in tone\n1F469 1F3FD 200D 1F373 \; fully-qualified - # 👩🏽‍🍳 E4.0 woman cook: medium skin tone\n1F469 1F3FE 200D 1F3 - 73 \; fully-qualified # 👩🏾‍🍳 E4.0 woman - cook: medium-dark skin tone\n1F469 1F3FF 200D 1F373 \ - ; fully-qualified # 👩🏿‍🍳 E4.0 woman cook: dark skin tone\n1 - F9D1 200D 1F527 \; fully-qualified # 🧑‍ - 🔧 E12.1 mechanic\n1F9D1 1F3FB 200D 1F527 \; fully-q - ualified # 🧑🏻‍🔧 E12.1 mechanic: light skin tone\n1F9D1 1F3F - C 200D 1F527 \; fully-qualified # 🧑🏼‍🔧 - E12.1 mechanic: medium-light skin tone\n1F9D1 1F3FD 200D 1F527 - \; fully-qualified # 🧑🏽‍🔧 E12.1 mechanic: medium s - kin tone\n1F9D1 1F3FE 200D 1F527 \; fully-qualified - # 🧑🏾‍🔧 E12.1 mechanic: medium-dark skin tone\n1F9D1 1F3FF 200 - D 1F527 \; fully-qualified # 🧑🏿‍🔧 E12.1 - mechanic: dark skin tone\n1F468 200D 1F527 \; f - ully-qualified # 👨‍🔧 E4.0 man mechanic\n1F468 1F3FB 200D 1F527 - \; fully-qualified # 👨🏻‍🔧 E4.0 man mec - hanic: light skin tone\n1F468 1F3FC 200D 1F527 \; full - y-qualified # 👨🏼‍🔧 E4.0 man mechanic: medium-light skin ton - e\n1F468 1F3FD 200D 1F527 \; fully-qualified # - 👨🏽‍🔧 E4.0 man mechanic: medium skin tone\n1F468 1F3FE 200D 1F52 - 7 \; fully-qualified # 👨🏾‍🔧 E4.0 man me - chanic: medium-dark skin tone\n1F468 1F3FF 200D 1F527 - \; fully-qualified # 👨🏿‍🔧 E4.0 man mechanic: dark skin tone - \n1F469 200D 1F527 \; fully-qualified # 👩 - ‍🔧 E4.0 woman mechanic\n1F469 1F3FB 200D 1F527 \; - fully-qualified # 👩🏻‍🔧 E4.0 woman mechanic: light skin ton - e\n1F469 1F3FC 200D 1F527 \; fully-qualified # � - �🏼‍🔧 E4.0 woman mechanic: medium-light skin tone\n1F469 1F3FD 20 - 0D 1F527 \; fully-qualified # 👩🏽‍🔧 E4.0 - woman mechanic: medium skin tone\n1F469 1F3FE 200D 1F527 - \; fully-qualified # 👩🏾‍🔧 E4.0 woman mechanic: medium-d - ark skin tone\n1F469 1F3FF 200D 1F527 \; fully-qualifi - ed # 👩🏿‍🔧 E4.0 woman mechanic: dark skin tone\n1F9D1 200D 1 - F3ED \; fully-qualified # 🧑‍🏭 E12.1 - factory worker\n1F9D1 1F3FB 200D 1F3ED \; fully-qualif - ied # 🧑🏻‍🏭 E12.1 factory worker: light skin tone\n1F9D1 1F3 - FC 200D 1F3ED \; fully-qualified # 🧑🏼‍🏭 - E12.1 factory worker: medium-light skin tone\n1F9D1 1F3FD 200D 1F3ED - \; fully-qualified # 🧑🏽‍🏭 E12.1 factory wor - ker: medium skin tone\n1F9D1 1F3FE 200D 1F3ED \; fully - -qualified # 🧑🏾‍🏭 E12.1 factory worker: medium-dark skin to - ne\n1F9D1 1F3FF 200D 1F3ED \; fully-qualified # - 🧑🏿‍🏭 E12.1 factory worker: dark skin tone\n1F468 200D 1F3ED - \; fully-qualified # 👨‍🏭 E4.0 man facto - ry worker\n1F468 1F3FB 200D 1F3ED \; fully-qualified - # 👨🏻‍🏭 E4.0 man factory worker: light skin tone\n1F468 1F3FC - 200D 1F3ED \; fully-qualified # 👨🏼‍🏭 E - 4.0 man factory worker: medium-light skin tone\n1F468 1F3FD 200D 1F3ED - \; fully-qualified # 👨🏽‍�� E4.0 man facto - ry worker: medium skin tone\n1F468 1F3FE 200D 1F3ED \; - fully-qualified # 👨🏾‍🏭 E4.0 man factory worker: medium-dar - k skin tone\n1F468 1F3FF 200D 1F3ED \; fully-qualified - # 👨🏿‍🏭 E4.0 man factory worker: dark skin tone\n1F469 200D - 1F3ED \; fully-qualified # 👩‍🏭 E4.0 - woman factory worker\n1F469 1F3FB 200D 1F3ED \; fully - -qualified # 👩🏻‍🏭 E4.0 woman factory worker: light skin ton - e\n1F469 1F3FC 200D 1F3ED \; fully-qualified # - 👩🏼‍🏭 E4.0 woman factory worker: medium-light skin tone\n1F469 1 - F3FD 200D 1F3ED \; fully-qualified # 👩🏽‍ - 🏭 E4.0 woman factory worker: medium skin tone\n1F469 1F3FE 200D 1F3ED - \; fully-qualified # 👩🏾‍🏭 E4.0 woman fac - tory worker: medium-dark skin tone\n1F469 1F3FF 200D 1F3ED - \; fully-qualified # 👩🏿‍🏭 E4.0 woman factory worker: d - ark skin tone\n1F9D1 200D 1F4BC \; fully-qualifi - ed # 🧑‍💼 E12.1 office worker\n1F9D1 1F3FB 200D 1F4BC - \; fully-qualified # 🧑🏻‍💼 E12.1 office worker: l - ight skin tone\n1F9D1 1F3FC 200D 1F4BC \; fully-qualif - ied # 🧑🏼‍💼 E12.1 office worker: medium-light skin tone\n1F9 - D1 1F3FD 200D 1F4BC \; fully-qualified # 🧑🏽 - ‍💼 E12.1 office worker: medium skin tone\n1F9D1 1F3FE 200D 1F4BC - \; fully-qualified # 🧑🏾‍💼 E12.1 office work - er: medium-dark skin tone\n1F9D1 1F3FF 200D 1F4BC \; f - ully-qualified # 🧑🏿‍💼 E12.1 office worker: dark skin tone\n - 1F468 200D 1F4BC \; fully-qualified # 👨 - ‍💼 E4.0 man office worker\n1F468 1F3FB 200D 1F4BC - \; fully-qualified # 👨🏻‍💼 E4.0 man office worker: light sk - in tone\n1F468 1F3FC 200D 1F4BC \; fully-qualified - # 👨🏼‍💼 E4.0 man office worker: medium-light skin tone\n1F468 1 - F3FD 200D 1F4BC \; fully-qualified # 👨🏽‍ - 💼 E4.0 man office worker: medium skin tone\n1F468 1F3FE 200D 1F4BC - \; fully-qualified # 👨🏾‍💼 E4.0 man office w - orker: medium-dark skin tone\n1F468 1F3FF 200D 1F4BC \ - ; fully-qualified # 👨🏿‍💼 E4.0 man office worker: dark skin - tone\n1F469 200D 1F4BC \; fully-qualified # - 👩‍💼 E4.0 woman office worker\n1F469 1F3FB 200D 1F4BC - \; fully-qualified # 👩🏻‍💼 E4.0 woman office worker: - light skin tone\n1F469 1F3FC 200D 1F4BC \; fully-quali - fied # 👩🏼‍💼 E4.0 woman office worker: medium-light skin ton - e\n1F469 1F3FD 200D 1F4BC \; fully-qualified # - 👩🏽‍💼 E4.0 woman office worker: medium skin tone\n1F469 1F3FE 20 - 0D 1F4BC \; fully-qualified # 👩🏾‍💼 E4.0 - woman office worker: medium-dark skin tone\n1F469 1F3FF 200D 1F4BC - \; fully-qualified # 👩🏿‍💼 E4.0 woman office w - orker: dark skin tone\n1F9D1 200D 1F52C \; fully - -qualified # 🧑‍🔬 E12.1 scientist\n1F9D1 1F3FB 200D 1F52C - \; fully-qualified # 🧑🏻‍🔬 E12.1 scientist: l - ight skin tone\n1F9D1 1F3FC 200D 1F52C \; fully-qualif - ied # 🧑🏼‍🔬 E12.1 scientist: medium-light skin tone\n1F9D1 1 - F3FD 200D 1F52C \; fully-qualified # 🧑🏽‍ - 🔬 E12.1 scientist: medium skin tone\n1F9D1 1F3FE 200D 1F52C - \; fully-qualified # 🧑🏾‍🔬 E12.1 scientist: medium- - dark skin tone\n1F9D1 1F3FF 200D 1F52C \; fully-qualif - ied # 🧑🏿‍🔬 E12.1 scientist: dark skin tone\n1F468 200D 1F52 - C \; fully-qualified # 👨‍🔬 E4.0 man - scientist\n1F468 1F3FB 200D 1F52C \; fully-qualified - # 👨🏻‍🔬 E4.0 man scientist: light skin tone\n1F468 1F3FC 200D - 1F52C \; fully-qualified # 👨🏼‍🔬 E4.0 m - an scientist: medium-light skin tone\n1F468 1F3FD 200D 1F52C - \; fully-qualified # 👨🏽‍🔬 E4.0 man scientist: medium - skin tone\n1F468 1F3FE 200D 1F52C \; fully-qualified - # 👨🏾‍🔬 E4.0 man scientist: medium-dark skin tone\n1F468 1F3 - FF 200D 1F52C \; fully-qualified # 👨🏿‍🔬 - E4.0 man scientist: dark skin tone\n1F469 200D 1F52C - \; fully-qualified # 👩‍🔬 E4.0 woman scientist\n1F469 1F3 - FB 200D 1F52C \; fully-qualified # 👩��‍ - 🔬 E4.0 woman scientist: light skin tone\n1F469 1F3FC 200D 1F52C - \; fully-qualified # 👩🏼‍🔬 E4.0 woman scientist - : medium-light skin tone\n1F469 1F3FD 200D 1F52C \; fu - lly-qualified # 👩🏽‍🔬 E4.0 woman scientist: medium skin tone - \n1F469 1F3FE 200D 1F52C \; fully-qualified # 👩 - 🏾‍🔬 E4.0 woman scientist: medium-dark skin tone\n1F469 1F3FF 200D - 1F52C \; fully-qualified # 👩🏿‍🔬 E4.0 wo - man scientist: dark skin tone\n1F9D1 200D 1F4BB - \; fully-qualified # 🧑‍💻 E12.1 technologist\n1F9D1 1F3FB 200D - 1F4BB \; fully-qualified # 🧑🏻‍💻 E12.1 t - echnologist: light skin tone\n1F9D1 1F3FC 200D 1F4BB \ - ; fully-qualified # 🧑🏼‍💻 E12.1 technologist: medium-light s - kin tone\n1F9D1 1F3FD 200D 1F4BB \; fully-qualified - # 🧑🏽‍💻 E12.1 technologist: medium skin tone\n1F9D1 1F3FE 200D - 1F4BB \; fully-qualified # 🧑🏾‍💻 E12.1 - technologist: medium-dark skin tone\n1F9D1 1F3FF 200D 1F4BB - \; fully-qualified # 🧑🏿‍💻 E12.1 technologist: dark sk - in tone\n1F468 200D 1F4BB \; fully-qualified - # 👨‍💻 E4.0 man technologist\n1F468 1F3FB 200D 1F4BB - \; fully-qualified # 👨🏻‍💻 E4.0 man technologist: lig - ht skin tone\n1F468 1F3FC 200D 1F4BB \; fully-qualifie - d # 👨🏼‍💻 E4.0 man technologist: medium-light skin tone\n1F4 - 68 1F3FD 200D 1F4BB \; fully-qualified # 👨🏽 - ‍💻 E4.0 man technologist: medium skin tone\n1F468 1F3FE 200D 1F4BB - \; fully-qualified # 👨🏾‍💻 E4.0 man techno - logist: medium-dark skin tone\n1F468 1F3FF 200D 1F4BB - \; fully-qualified # 👨🏿‍💻 E4.0 man technologist: dark skin - tone\n1F469 200D 1F4BB \; fully-qualified # - 👩‍💻 E4.0 woman technologist\n1F469 1F3FB 200D 1F4BB - \; fully-qualified # 👩🏻‍💻 E4.0 woman technologist: li - ght skin tone\n1F469 1F3FC 200D 1F4BB \; fully-qualifi - ed # 👩🏼‍💻 E4.0 woman technologist: medium-light skin tone\n - 1F469 1F3FD 200D 1F4BB \; fully-qualified # 👩 - 🏽‍💻 E4.0 woman technologist: medium skin tone\n1F469 1F3FE 200D 1F - 4BB \; fully-qualified # 👩🏾‍💻 E4.0 woma - n technologist: medium-dark skin tone\n1F469 1F3FF 200D 1F4BB - \; fully-qualified # 👩🏿‍💻 E4.0 woman technologist: - dark skin tone\n1F9D1 200D 1F3A4 \; fully-qualif - ied # 🧑‍🎤 E12.1 singer\n1F9D1 1F3FB 200D 1F3A4 - \; fully-qualified # 🧑🏻‍🎤 E12.1 singer: light skin ton - e\n1F9D1 1F3FC 200D 1F3A4 \; fully-qualified # - 🧑🏼‍�� E12.1 singer: medium-light skin tone\n1F9D1 1F3FD 200D 1 - F3A4 \; fully-qualified # 🧑🏽‍🎤 E12.1 si - nger: medium skin tone\n1F9D1 1F3FE 200D 1F3A4 \; full - y-qualified # 🧑🏾‍🎤 E12.1 singer: medium-dark skin tone\n1F9 - D1 1F3FF 200D 1F3A4 \; fully-qualified # 🧑🏿 - ‍🎤 E12.1 singer: dark skin tone\n1F468 200D 1F3A4 - \; fully-qualified # 👨‍🎤 E4.0 man singer\n1F468 1F3FB 2 - 00D 1F3A4 \; fully-qualified # 👨🏻‍🎤 E4. - 0 man singer: light skin tone\n1F468 1F3FC 200D 1F3A4 - \; fully-qualified # 👨🏼‍🎤 E4.0 man singer: medium-light ski - n tone\n1F468 1F3FD 200D 1F3A4 \; fully-qualified - # 👨🏽‍🎤 E4.0 man singer: medium skin tone\n1F468 1F3FE 200D 1F3A - 4 \; fully-qualified # 👨🏾‍🎤 E4.0 man si - nger: medium-dark skin tone\n1F468 1F3FF 200D 1F3A4 \; - fully-qualified # 👨🏿‍🎤 E4.0 man singer: dark skin tone\n1F - 469 200D 1F3A4 \; fully-qualified # 👩‍ - 🎤 E4.0 woman singer\n1F469 1F3FB 200D 1F3A4 \; full - y-qualified # 👩🏻‍🎤 E4.0 woman singer: light skin tone\n1F46 - 9 1F3FC 200D 1F3A4 \; fully-qualified # 👩🏼 - ‍🎤 E4.0 woman singer: medium-light skin tone\n1F469 1F3FD 200D 1F3A4 - \; fully-qualified # 👩🏽‍🎤 E4.0 woman si - nger: medium skin tone\n1F469 1F3FE 200D 1F3A4 \; full - y-qualified # 👩🏾‍🎤 E4.0 woman singer: medium-dark skin tone - \n1F469 1F3FF 200D 1F3A4 \; fully-qualified # 👩 - 🏿‍�� E4.0 woman singer: dark skin tone\n1F9D1 200D 1F3A8 - \; fully-qualified # 🧑‍🎨 E12.1 artist\n1F9D1 - 1F3FB 200D 1F3A8 \; fully-qualified # 🧑🏻‍ - 🎨 E12.1 artist: light skin tone\n1F9D1 1F3FC 200D 1F3A8 - \; fully-qualified # 🧑🏼‍🎨 E12.1 artist: medium-light s - kin tone\n1F9D1 1F3FD 200D 1F3A8 \; fully-qualified - # 🧑🏽‍🎨 E12.1 artist: medium skin tone\n1F9D1 1F3FE 200D 1F3A8 - \; fully-qualified # 🧑��‍🎨 E12.1 arti - st: medium-dark skin tone\n1F9D1 1F3FF 200D 1F3A8 \; f - ully-qualified # 🧑🏿‍🎨 E12.1 artist: dark skin tone\n1F468 2 - 00D 1F3A8 \; fully-qualified # 👨‍🎨 E - 4.0 man artist\n1F468 1F3FB 200D 1F3A8 \; fully-qualif - ied # 👨🏻‍🎨 E4.0 man artist: light skin tone\n1F468 1F3FC 20 - 0D 1F3A8 \; fully-qualified # 👨🏼‍🎨 E4.0 - man artist: medium-light skin tone\n1F468 1F3FD 200D 1F3A8 - \; fully-qualified # 👨🏽‍🎨 E4.0 man artist: medium ski - n tone\n1F468 1F3FE 200D 1F3A8 \; fully-qualified - # 👨🏾‍🎨 E4.0 man artist: medium-dark skin tone\n1F468 1F3FF 200D - 1F3A8 \; fully-qualified # 👨🏿‍🎨 E4.0 m - an artist: dark skin tone\n1F469 200D 1F3A8 \; f - ully-qualified # 👩‍🎨 E4.0 woman artist\n1F469 1F3FB 200D 1F3A8 - \; fully-qualified # 👩🏻‍🎨 E4.0 woman a - rtist: light skin tone\n1F469 1F3FC 200D 1F3A8 \; full - y-qualified # 👩🏼‍🎨 E4.0 woman artist: medium-light skin ton - e\n1F469 1F3FD 200D 1F3A8 \; fully-qualified # - 👩🏽‍�� E4.0 woman artist: medium skin tone\n1F469 1F3FE 200D 1F - 3A8 \; fully-qualified # 👩🏾‍🎨 E4.0 woma - n artist: medium-dark skin tone\n1F469 1F3FF 200D 1F3A8 - \; fully-qualified # 👩🏿‍🎨 E4.0 woman artist: dark skin to - ne\n1F9D1 200D 2708 FE0F \; fully-qualified # - 🧑‍✈️ E12.1 pilot\n1F9D1 200D 2708 \; m - inimally-qualified # 🧑‍✈ E12.1 pilot\n1F9D1 1F3FB 200D 2708 FE0F - \; fully-qualified # 🧑🏻‍✈️ E12.1 pilot: ligh - t skin tone\n1F9D1 1F3FB 200D 2708 \; minimally-quali - fied # 🧑🏻‍✈ E12.1 pilot: light skin tone\n1F9D1 1F3FC 200D 2708 - FE0F \; fully-qualified # 🧑🏼‍✈️ E12.1 pilo - t: medium-light skin tone\n1F9D1 1F3FC 200D 2708 \; m - inimally-qualified # 🧑🏼‍✈ E12.1 pilot: medium-light skin tone\n1 - F9D1 1F3FD 200D 2708 FE0F \; fully-qualified # 🧑 - 🏽‍✈️ E12.1 pilot: medium skin tone\n1F9D1 1F3FD 200D 2708 - \; minimally-qualified # 🧑🏽‍✈ E12.1 pilot: medium - skin tone\n1F9D1 1F3FE 200D 2708 FE0F \; fully-qualified - # 🧑🏾‍✈️ E12.1 pilot: medium-dark skin tone\n1F9D1 1F3FE 200 - D 2708 \; minimally-qualified # 🧑🏾‍✈ E12.1 - pilot: medium-dark skin tone\n1F9D1 1F3FF 200D 2708 FE0F \ - ; fully-qualified # 🧑🏿‍✈️ E12.1 pilot: dark skin tone\n1F9 - D1 1F3FF 200D 2708 \; minimally-qualified # 🧑🏿 - ‍✈ E12.1 pilot: dark skin tone\n1F468 200D 2708 FE0F - \; fully-qualified # 👨‍✈️ E4.0 man pilot\n1F468 200D 270 - 8 \; minimally-qualified # 👨‍✈ E4.0 man - pilot\n1F468 1F3FB 200D 2708 FE0F \; fully-qualified # - 👨🏻‍✈️ E4.0 man pilot: light skin tone\n1F468 1F3FB 200D 2708 - \; minimally-qualified # 👨🏻‍✈ E4.0 man pilo - t: light skin tone\n1F468 1F3FC 200D 2708 FE0F \; fully-qu - alified # 👨🏼‍✈️ E4.0 man pilot: medium-light skin tone\n1F - 468 1F3FC 200D 2708 \; minimally-qualified # 👨🏼 - ‍✈ E4.0 man pilot: medium-light skin tone\n1F468 1F3FD 200D 2708 FE0F - \; fully-qualified # 👨🏽‍✈️ E4.0 man pilot: - medium skin tone\n1F468 1F3FD 200D 2708 \; minimally - -qualified # 👨🏽‍✈ E4.0 man pilot: medium skin tone\n1F468 1F3FE - 200D 2708 FE0F \; fully-qualified # 👨🏾‍✈️ - E4.0 man pilot: medium-dark skin tone\n1F468 1F3FE 200D 2708 - \; minimally-qualified # 👨🏾‍✈ E4.0 man pilot: medium-dar - k skin tone\n1F468 1F3FF 200D 2708 FE0F \; fully-qualified - # 👨🏿‍✈️ E4.0 man pilot: dark skin tone\n1F468 1F3FF 200D - 2708 \; minimally-qualified # 👨🏿‍✈ E4.0 man - pilot: dark skin tone\n1F469 200D 2708 FE0F \; full - y-qualified # 👩‍✈️ E4.0 woman pilot\n1F469 200D 2708 - \; minimally-qualified # 👩‍✈ E4.0 woman pilot\n1 - F469 1F3FB 200D 2708 FE0F \; fully-qualified # 👩 - 🏻‍✈️ E4.0 woman pilot: light skin tone\n1F469 1F3FB 200D 2708 - \; minimally-qualified # 👩🏻‍✈ E4.0 woman pilot - : light skin tone\n1F469 1F3FC 200D 2708 FE0F \; fully-qua - lified # 👩🏼‍✈️ E4.0 woman pilot: medium-light skin tone\n1 - F469 1F3FC 200D 2708 \; minimally-qualified # 👩 - 🏼‍✈ E4.0 woman pilot: medium-light skin tone\n1F469 1F3FD 200D 2708 - FE0F \; fully-qualified # 👩🏽‍✈️ E4.0 woma - n pilot: medium skin tone\n1F469 1F3FD 200D 2708 \; m - inimally-qualified # 👩🏽‍✈ E4.0 woman pilot: medium skin tone\n1F - 469 1F3FE 200D 2708 FE0F \; fully-qualified # 👩🏾 - ‍✈️ E4.0 woman pilot: medium-dark skin tone\n1F469 1F3FE 200D 2708 - \; minimally-qualified # 👩🏾‍✈ E4.0 woman pil - ot: medium-dark skin tone\n1F469 1F3FF 200D 2708 FE0F \; f - ully-qualified # 👩🏿‍✈️ E4.0 woman pilot: dark skin tone\n1 - F469 1F3FF 200D 2708 \; minimally-qualified # 👩 - 🏿‍✈ E4.0 woman pilot: dark skin tone\n1F9D1 200D 1F680 - \; fully-qualified # 🧑‍🚀 E12.1 astronaut\n1F9D1 - 1F3FB 200D 1F680 \; fully-qualified # 🧑🏻‍ - 🚀 E12.1 astronaut: light skin tone\n1F9D1 1F3FC 200D 1F680 - \; fully-qualified # 🧑🏼‍🚀 E12.1 astronaut: medium-l - ight skin tone\n1F9D1 1F3FD 200D 1F680 \; fully-qualif - ied # 🧑🏽‍🚀 E12.1 astronaut: medium skin tone\n1F9D1 1F3FE 2 - 00D 1F680 \; fully-qualified # 🧑🏾‍🚀 E12 - .1 astronaut: medium-dark skin tone\n1F9D1 1F3FF 200D 1F680 - \; fully-qualified # 🧑🏿‍🚀 E12.1 astronaut: dark skin - tone\n1F468 200D 1F680 \; fully-qualified # - 👨‍🚀 E4.0 man astronaut\n1F468 1F3FB 200D 1F680 - \; fully-qualified # 👨🏻‍🚀 E4.0 man astronaut: light skin t - one\n1F468 1F3FC 200D 1F680 \; fully-qualified # - 👨🏼‍🚀 E4.0 man astronaut: medium-light skin tone\n1F468 1F3FD 20 - 0D 1F680 \; fully-qualified # 👨🏽‍🚀 E4.0 - man astronaut: medium skin tone\n1F468 1F3FE 200D 1F680 - \; fully-qualified # 👨🏾‍🚀 E4.0 man astronaut: medium-dar - k skin tone\n1F468 1F3FF 200D 1F680 \; fully-qualified - # 👨🏿‍🚀 E4.0 man astronaut: dark skin tone\n1F469 200D 1F68 - 0 \; fully-qualified # 👩‍🚀 E4.0 woma - n astronaut\n1F469 1F3FB 200D 1F680 \; fully-qualified - # 👩🏻‍🚀 E4.0 woman astronaut: light skin tone\n1F469 1F3FC - 200D 1F680 \; fully-qualified # 👩🏼‍🚀 E4 - .0 woman astronaut: medium-light skin tone\n1F469 1F3FD 200D 1F680 - \; fully-qualified # 👩🏽‍🚀 E4.0 woman astronaut - : medium skin tone\n1F469 1F3FE 200D 1F680 \; fully-qu - alified # 👩🏾‍🚀 E4.0 woman astronaut: medium-dark skin tone\ - n1F469 1F3FF 200D 1F680 \; fully-qualified # 👩 - 🏿‍🚀 E4.0 woman astronaut: dark skin tone\n1F9D1 200D 1F692 - \; fully-qualified # 🧑‍🚒 E12.1 firefighter\ - n1F9D1 1F3FB 200D 1F692 \; fully-qualified # 🧑 - ��‍🚒 E12.1 firefighter: light skin tone\n1F9D1 1F3FC 200D 1F692 - \; fully-qualified # 🧑🏼‍🚒 E12.1 firefigh - ter: medium-light skin tone\n1F9D1 1F3FD 200D 1F692 \; - fully-qualified # 🧑🏽‍🚒 E12.1 firefighter: medium skin tone - \n1F9D1 1F3FE 200D 1F692 \; fully-qualified # 🧑 - 🏾‍🚒 E12.1 firefighter: medium-dark skin tone\n1F9D1 1F3FF 200D 1F6 - 92 \; fully-qualified # 🧑🏿‍🚒 E12.1 fire - fighter: dark skin tone\n1F468 200D 1F692 \; ful - ly-qualified # 👨‍🚒 E4.0 man firefighter\n1F468 1F3FB 200D 1F69 - 2 \; fully-qualified # 👨🏻‍🚒 E4.0 man fi - refighter: light skin tone\n1F468 1F3FC 200D 1F692 \; - fully-qualified # 👨🏼‍🚒 E4.0 man firefighter: medium-light s - kin tone\n1F468 1F3FD 200D 1F692 \; fully-qualified - # 👨🏽‍🚒 E4.0 man firefighter: medium skin tone\n1F468 1F3FE 20 - 0D 1F692 \; fully-qualified # 👨🏾‍🚒 E4.0 - man firefighter: medium-dark skin tone\n1F468 1F3FF 200D 1F692 - \; fully-qualified # 👨🏿‍🚒 E4.0 man firefighter: d - ark skin tone\n1F469 200D 1F692 \; fully-qualifi - ed # 👩‍🚒 E4.0 woman firefighter\n1F469 1F3FB 200D 1F692 - \; fully-qualified # 👩🏻‍🚒 E4.0 woman firefigh - ter: light skin tone\n1F469 1F3FC 200D 1F692 \; fully- - qualified # 👩🏼‍🚒 E4.0 woman firefighter: medium-light skin - tone\n1F469 1F3FD 200D 1F692 \; fully-qualified # - 👩🏽‍🚒 E4.0 woman firefighter: medium skin tone\n1F469 1F3FE 200D - 1F692 \; fully-qualified # 👩🏾‍🚒 E4.0 w - oman firefighter: medium-dark skin tone\n1F469 1F3FF 200D 1F692 - \; fully-qualified # 👩🏿‍🚒 E4.0 woman firefighter: - dark skin tone\n1F46E \; fully-quali - fied # 👮 E2.0 police officer\n1F46E 1F3FB - \; fully-qualified # 👮🏻 E2.0 police officer: light skin to - ne\n1F46E 1F3FC \; fully-qualified # - 👮🏼 E2.0 police officer: medium-light skin tone\n1F46E 1F3FD - \; fully-qualified # 👮🏽 E2.0 police offic - er: medium skin tone\n1F46E 1F3FE \; fully- - qualified # 👮🏾 E2.0 police officer: medium-dark skin tone\n1F46E - 1F3FF \; fully-qualified # 👮🏿 E2 - .0 police officer: dark skin tone\n1F46E 200D 2642 FE0F - \; fully-qualified # 👮‍♂️ E4.0 man police officer\n1F46E - 200D 2642 \; minimally-qualified # 👮‍♂ E - 4.0 man police officer\n1F46E 1F3FB 200D 2642 FE0F \; full - y-qualified # 👮🏻‍♂️ E4.0 man police officer: light skin to - ne\n1F46E 1F3FB 200D 2642 \; minimally-qualified # - 👮🏻‍♂ E4.0 man police officer: light skin tone\n1F46E 1F3FC 200D - 2642 FE0F \; fully-qualified # 👮🏼‍♂️ E4.0 - man police officer: medium-light skin tone\n1F46E 1F3FC 200D 2642 - \; minimally-qualified # 👮🏼‍♂ E4.0 man police offic - er: medium-light skin tone\n1F46E 1F3FD 200D 2642 FE0F \; - fully-qualified # 👮🏽‍♂️ E4.0 man police officer: medium sk - in tone\n1F46E 1F3FD 200D 2642 \; minimally-qualified - # 👮🏽‍♂ E4.0 man police officer: medium skin tone\n1F46E 1F3FE 2 - 00D 2642 FE0F \; fully-qualified # 👮🏾‍♂️ E - 4.0 man police officer: medium-dark skin tone\n1F46E 1F3FE 200D 2642 - \; minimally-qualified # 👮🏾‍♂ E4.0 man police of - ficer: medium-dark skin tone\n1F46E 1F3FF 200D 2642 FE0F \ - ; fully-qualified # 👮🏿‍♂️ E4.0 man police officer: dark sk - in tone\n1F46E 1F3FF 200D 2642 \; minimally-qualified - # 👮🏿‍♂ E4.0 man police officer: dark skin tone\n1F46E 200D 2640 - FE0F \; fully-qualified # 👮‍♀️ E4.0 wo - man police officer\n1F46E 200D 2640 \; minimall - y-qualified # 👮‍♀ E4.0 woman police officer\n1F46E 1F3FB 200D 2640 - FE0F \; fully-qualified # 👮🏻‍♀️ E4.0 woman - police officer: light skin tone\n1F46E 1F3FB 200D 2640 - \; minimally-qualified # 👮🏻‍♀ E4.0 woman police officer: ligh - t skin tone\n1F46E 1F3FC 200D 2640 FE0F \; fully-qualified - # 👮🏼‍♀️ E4.0 woman police officer: medium-light skin tone - \n1F46E 1F3FC 200D 2640 \; minimally-qualified # 👮 - 🏼‍♀ E4.0 woman police officer: medium-light skin tone\n1F46E 1F3FD - 200D 2640 FE0F \; fully-qualified # 👮🏽‍♀️ - E4.0 woman police officer: medium skin tone\n1F46E 1F3FD 200D 2640 - \; minimally-qualified # 👮🏽‍♀ E4.0 woman police of - ficer: medium skin tone\n1F46E 1F3FE 200D 2640 FE0F \; ful - ly-qualified # 👮🏾‍♀️ E4.0 woman police officer: medium-dar - k skin tone\n1F46E 1F3FE 200D 2640 \; minimally-quali - fied # 👮🏾‍♀ E4.0 woman police officer: medium-dark skin tone\n1F - 46E 1F3FF 200D 2640 FE0F \; fully-qualified # 👮🏿 - ‍♀️ E4.0 woman police officer: dark skin tone\n1F46E 1F3FF 200D 2640 - \; minimally-qualified # 👮🏿‍♀ E4.0 woman p - olice officer: dark skin tone\n1F575 FE0F - \; fully-qualified # 🕵️ E2.0 detective\n1F575 - \; unqualified # 🕵 E2.0 detective\n1F575 1F3F - B \; fully-qualified # 🕵🏻 E2.0 de - tective: light skin tone\n1F575 1F3FC \; fu - lly-qualified # 🕵🏼 E2.0 detective: medium-light skin tone\n1F575 - 1F3FD \; fully-qualified # 🕵🏽 E2 - .0 detective: medium skin tone\n1F575 1F3FE - \; fully-qualified # 🕵🏾 E2.0 detective: medium-dark skin tone\n - 1F575 1F3FF \; fully-qualified # 🕵 - 🏿 E2.0 detective: dark skin tone\n1F575 FE0F 200D 2642 FE0F - \; fully-qualified # 🕵️‍♂️ E4.0 man detective\n1F575 - 200D 2642 FE0F \; unqualified # 🕵‍♂ - ️ E4.0 man detective\n1F575 FE0F 200D 2642 \; unqu - alified # 🕵️‍♂ E4.0 man detective\n1F575 200D 2642 - \; unqualified # 🕵‍♂ E4.0 man detectiv - e\n1F575 1F3FB 200D 2642 FE0F \; fully-qualified # � - �🏻‍♂️ E4.0 man detective: light skin tone\n1F575 1F3FB 200D 264 - 2 \; minimally-qualified # 🕵🏻‍♂ E4.0 man de - tective: light skin tone\n1F575 1F3FC 200D 2642 FE0F \; fu - lly-qualified # ��🏼‍♂️ E4.0 man detective: medium-light s - kin tone\n1F575 1F3FC 200D 2642 \; minimally-qualifie - d # 🕵🏼‍♂ E4.0 man detective: medium-light skin tone\n1F575 1F3FD - 200D 2642 FE0F \; fully-qualified # 🕵🏽‍♂️ - E4.0 man detective: medium skin tone\n1F575 1F3FD 200D 2642 - \; minimally-qualified # 🕵🏽‍♂ E4.0 man detective: medium - skin tone\n1F575 1F3FE 200D 2642 FE0F \; fully-qualified - # 🕵🏾‍♂️ E4.0 man detective: medium-dark skin tone\n1F575 1 - F3FE 200D 2642 \; minimally-qualified # 🕵🏾‍ - ♂ E4.0 man detective: medium-dark skin tone\n1F575 1F3FF 200D 2642 FE0F - \; fully-qualified # 🕵🏿‍♂️ E4.0 man detect - ive: dark skin tone\n1F575 1F3FF 200D 2642 \; minimal - ly-qualified # 🕵🏿‍♂ E4.0 man detective: dark skin tone\n1F575 FE - 0F 200D 2640 FE0F \; fully-qualified # 🕵️‍♀ - ️ E4.0 woman detective\n1F575 200D 2640 FE0F \; un - qualified # 🕵‍♀️ E4.0 woman detective\n1F575 FE0F 200D 26 - 40 \; unqualified # 🕵️‍♀ E4.0 woman - detective\n1F575 200D 2640 \; unqualified - # 🕵‍♀ E4.0 woman detective\n1F575 1F3FB 200D 2640 FE0F - \; fully-qualified # 🕵🏻‍♀️ E4.0 woman detective: l - ight skin tone\n1F575 1F3FB 200D 2640 \; minimally-qu - alified # ��🏻‍♀ E4.0 woman detective: light skin tone\n1F575 1F - 3FC 200D 2640 FE0F \; fully-qualified # 🕵🏼‍♀ - ️ E4.0 woman detective: medium-light skin tone\n1F575 1F3FC 200D 2640 - \; minimally-qualified # 🕵🏼‍♀ E4.0 woman dete - ctive: medium-light skin tone\n1F575 1F3FD 200D 2640 FE0F - \; fully-qualified # 🕵🏽‍♀️ E4.0 woman detective: medium sk - in tone\n1F575 1F3FD 200D 2640 \; minimally-qualified - # 🕵🏽‍♀ E4.0 woman detective: medium skin tone\n1F575 1F3FE 200D - 2640 FE0F \; fully-qualified # 🕵🏾‍♀️ E4.0 - woman detective: medium-dark skin tone\n1F575 1F3FE 200D 2640 - \; minimally-qualified # 🕵🏾‍♀ E4.0 woman detective: me - dium-dark skin tone\n1F575 1F3FF 200D 2640 FE0F \; fully-q - ualified # 🕵🏿‍♀️ E4.0 woman detective: dark skin tone\n1F5 - 75 1F3FF 200D 2640 \; minimally-qualified # 🕵🏿 - ‍♀ E4.0 woman detective: dark skin tone\n1F482 - \; fully-qualified # 💂 E2.0 guard\n1F482 1F3FB - \; fully-qualified # 💂🏻 E2.0 guard: lig - ht skin tone\n1F482 1F3FC \; fully-qualifie - d # 💂🏼 E2.0 guard: medium-light skin tone\n1F482 1F3FD - \; fully-qualified # ��🏽 E2.0 guard: medi - um skin tone\n1F482 1F3FE \; fully-qualifie - d # 💂🏾 E2.0 guard: medium-dark skin tone\n1F482 1F3FF - \; fully-qualified # 💂🏿 E2.0 guard: dark sk - in tone\n1F482 200D 2642 FE0F \; fully-qualified - # 💂‍♂️ E4.0 man guard\n1F482 200D 2642 - \; minimally-qualified # 💂‍♂ E4.0 man guard\n1F482 1F3FB 200D 264 - 2 FE0F \; fully-qualified # 💂🏻‍♂️ E4.0 man - guard: light skin tone\n1F482 1F3FB 200D 2642 \; min - imally-qualified # ��🏻‍♂ E4.0 man guard: light skin tone\n1F482 - 1F3FC 200D 2642 FE0F \; fully-qualified # 💂🏼‍ - ♂️ E4.0 man guard: medium-light skin tone\n1F482 1F3FC 200D 2642 - \; minimally-qualified # 💂🏼‍♂ E4.0 man guard: me - dium-light skin tone\n1F482 1F3FD 200D 2642 FE0F \; fully- - qualified # 💂🏽‍♂️ E4.0 man guard: medium skin tone\n1F482 - 1F3FD 200D 2642 \; minimally-qualified # 💂🏽‍ - ♂ E4.0 man guard: medium skin tone\n1F482 1F3FE 200D 2642 FE0F - \; fully-qualified # 💂🏾‍♂️ E4.0 man guard: medium-d - ark skin tone\n1F482 1F3FE 200D 2642 \; minimally-qua - lified # 💂��‍♂ E4.0 man guard: medium-dark skin tone\n1F482 1F3 - FF 200D 2642 FE0F \; fully-qualified # 💂🏿‍♂ - ️ E4.0 man guard: dark skin tone\n1F482 1F3FF 200D 2642 - \; minimally-qualified # 💂🏿‍♂ E4.0 man guard: dark skin ton - e\n1F482 200D 2640 FE0F \; fully-qualified # - 💂‍♀️ E4.0 woman guard\n1F482 200D 2640 - \; minimally-qualified # 💂‍♀ E4.0 woman guard\n1F482 1F3FB 200D 26 - 40 FE0F \; fully-qualified # 💂🏻‍♀️ E4.0 wo - man guard: light skin tone\n1F482 1F3FB 200D 2640 \; - minimally-qualified # 💂🏻‍♀ E4.0 woman guard: light skin tone\n1F - 482 1F3FC 200D 2640 FE0F \; fully-qualified # 💂🏼 - ‍♀️ E4.0 woman guard: medium-light skin tone\n1F482 1F3FC 200D 2640 - \; minimally-qualified # 💂🏼‍♀ E4.0 woman gu - ard: medium-light skin tone\n1F482 1F3FD 200D 2640 FE0F \; - fully-qualified # 💂🏽‍♀️ E4.0 woman guard: medium skin ton - e\n1F482 1F3FD 200D 2640 \; minimally-qualified # - 💂🏽‍♀ E4.0 woman guard: medium skin tone\n1F482 1F3FE 200D 2640 F - E0F \; fully-qualified # 💂🏾‍♀️ E4.0 woman - guard: medium-dark skin tone\n1F482 1F3FE 200D 2640 \ - ; minimally-qualified # 💂🏾‍♀ E4.0 woman guard: medium-dark skin - tone\n1F482 1F3FF 200D 2640 FE0F \; fully-qualified # - 💂🏿‍♀️ E4.0 woman guard: dark skin tone\n1F482 1F3FF 200D 2640 - \; minimally-qualified # 💂🏿‍♀ E4.0 woman gu - ard: dark skin tone\n1F477 \; fully-q - ualified # 👷 E2.0 construction worker\n1F477 1F3FB - \; fully-qualified # 👷🏻 E2.0 construction worker: - light skin tone\n1F477 1F3FC \; fully-qual - ified # 👷🏼 E2.0 construction worker: medium-light skin tone\n1F4 - 77 1F3FD \; fully-qualified # 👷🏽 - E2.0 construction worker: medium skin tone\n1F477 1F3FE - \; fully-qualified # 👷🏾 E2.0 construction worker: m - edium-dark skin tone\n1F477 1F3FF \; fully- - qualified # 👷🏿 E2.0 construction worker: dark skin tone\n1F477 2 - 00D 2642 FE0F \; fully-qualified # 👷‍♂️ - E4.0 man construction worker\n1F477 200D 2642 - \; minimally-qualified # 👷‍♂ E4.0 man construction worker\n1F477 1F - 3FB 200D 2642 FE0F \; fully-qualified # 👷🏻‍♂ - ️ E4.0 man construction worker: light skin tone\n1F477 1F3FB 200D 2642 - \; minimally-qualified # 👷🏻‍♂ E4.0 man const - ruction worker: light skin tone\n1F477 1F3FC 200D 2642 FE0F - \; fully-qualified # 👷🏼‍♂️ E4.0 man construction worker: - medium-light skin tone\n1F477 1F3FC 200D 2642 \; min - imally-qualified # 👷🏼‍♂ E4.0 man construction worker: medium-lig - ht skin tone\n1F477 1F3FD 200D 2642 FE0F \; fully-qualifie - d # 👷🏽‍♂️ E4.0 man construction worker: medium skin tone\n - 1F477 1F3FD 200D 2642 \; minimally-qualified # 👷 - 🏽‍♂ E4.0 man construction worker: medium skin tone\n1F477 1F3FE 200 - D 2642 FE0F \; fully-qualified # 👷🏾‍♂️ E4. - 0 man construction worker: medium-dark skin tone\n1F477 1F3FE 200D 2642 - \; minimally-qualified # 👷🏾‍♂ E4.0 man constr - uction worker: medium-dark skin tone\n1F477 1F3FF 200D 2642 FE0F - \; fully-qualified # 👷🏿‍♂️ E4.0 man construction wo - rker: dark skin tone\n1F477 1F3FF 200D 2642 \; minima - lly-qualified # 👷🏿‍♂ E4.0 man construction worker: dark skin ton - e\n1F477 200D 2640 FE0F \; fully-qualified # - 👷‍♀️ E4.0 woman construction worker\n1F477 200D 2640 - \; minimally-qualified # 👷‍♀ E4.0 woman construction - worker\n1F477 1F3FB 200D 2640 FE0F \; fully-qualified - # 👷🏻‍♀️ E4.0 woman construction worker: light skin tone\n1F47 - 7 1F3FB 200D 2640 \; minimally-qualified # ��🏻 - ‍♀ E4.0 woman construction worker: light skin tone\n1F477 1F3FC 200D 2 - 640 FE0F \; fully-qualified # 👷🏼‍♀️ E4.0 w - oman construction worker: medium-light skin tone\n1F477 1F3FC 200D 2640 - \; minimally-qualified # 👷🏼‍♀ E4.0 woman cons - truction worker: medium-light skin tone\n1F477 1F3FD 200D 2640 FE0F - \; fully-qualified # 👷🏽‍♀️ E4.0 woman constructi - on worker: medium skin tone\n1F477 1F3FD 200D 2640 \; - minimally-qualified # 👷🏽‍♀ E4.0 woman construction worker: medi - um skin tone\n1F477 1F3FE 200D 2640 FE0F \; fully-qualifie - d # 👷🏾‍♀️ E4.0 woman construction worker: medium-dark skin - tone\n1F477 1F3FE 200D 2640 \; minimally-qualified # - 👷🏾‍♀ E4.0 woman construction worker: medium-dark skin tone\n1F4 - 77 1F3FF 200D 2640 FE0F \; fully-qualified # 👷🏿 - ‍♀️ E4.0 woman construction worker: dark skin tone\n1F477 1F3FF 200D - 2640 \; minimally-qualified # 👷🏿‍♀ E4.0 wo - man construction worker: dark skin tone\n1F934 - \; fully-qualified # 🤴 E4.0 prince\n1F934 1F3FB - \; fully-qualified # 🤴🏻 E4.0 prince: light - skin tone\n1F934 1F3FC \; fully-qualified - # 🤴🏼 E4.0 prince: medium-light skin tone\n1F934 1F3FD - \; fully-qualified # 🤴🏽 E4.0 prince: medium - skin tone\n1F934 1F3FE \; fully-qualified - # 🤴🏾 E4.0 prince: medium-dark skin tone\n1F934 1F3FF - \; fully-qualified # 🤴🏿 E4.0 prince: dark sk - in tone\n1F478 \; fully-qualified - # 👸 E2.0 princess\n1F478 1F3FB \; fully - -qualified # 👸🏻 E2.0 princess: light skin tone\n1F478 1F3FC - \; fully-qualified # 👸🏼 E2.0 princess - : medium-light skin tone\n1F478 1F3FD \; fu - lly-qualified # 👸🏽 E2.0 princess: medium skin tone\n1F478 1F3FE - \; fully-qualified # 👸🏾 E2.0 prin - cess: medium-dark skin tone\n1F478 1F3FF \; - fully-qualified # 👸🏿 E2.0 princess: dark skin tone\n1F473 - \; fully-qualified # 👳 E2.0 person - wearing turban\n1F473 1F3FB \; fully-qualif - ied # 👳🏻 E2.0 person wearing turban: light skin tone\n1F473 1F3F - C \; fully-qualified # 👳🏼 E2.0 pe - rson wearing turban: medium-light skin tone\n1F473 1F3FD - \; fully-qualified # 👳🏽 E2.0 person wearing turban - : medium skin tone\n1F473 1F3FE \; fully-qu - alified # 👳🏾 E2.0 person wearing turban: medium-dark skin tone\n - 1F473 1F3FF \; fully-qualified # 👳 - 🏿 E2.0 person wearing turban: dark skin tone\n1F473 200D 2642 FE0F - \; fully-qualified # 👳‍♂️ E4.0 man wearing - turban\n1F473 200D 2642 \; minimally-qualified - # 👳‍♂ E4.0 man wearing turban\n1F473 1F3FB 200D 2642 FE0F - \; fully-qualified # 👳🏻‍♂️ E4.0 man wearing turban: - light skin tone\n1F473 1F3FB 200D 2642 \; minimally- - qualified # 👳🏻‍♂ E4.0 man wearing turban: light skin tone\n1F473 - 1F3FC 200D 2642 FE0F \; fully-qualified # 👳🏼‍ - ♂️ E4.0 man wearing turban: medium-light skin tone\n1F473 1F3FC 200D 2 - 642 \; minimally-qualified # 👳🏼‍♂ E4.0 man - wearing turban: medium-light skin tone\n1F473 1F3FD 200D 2642 FE0F - \; fully-qualified # 👳🏽‍♂️ E4.0 man wearing turba - n: medium skin tone\n1F473 1F3FD 200D 2642 \; minimal - ly-qualified # 👳🏽‍♂ E4.0 man wearing turban: medium skin tone\n1 - F473 1F3FE 200D 2642 FE0F \; fully-qualified # 👳 - 🏾‍♂️ E4.0 man wearing turban: medium-dark skin tone\n1F473 1F3FE - 200D 2642 \; minimally-qualified # 👳🏾‍♂ E4. - 0 man wearing turban: medium-dark skin tone\n1F473 1F3FF 200D 2642 FE0F - \; fully-qualified # 👳🏿‍♂️ E4.0 man wearing - turban: dark skin tone\n1F473 1F3FF 200D 2642 \; mini - mally-qualified # 👳🏿‍♂ E4.0 man wearing turban: dark skin tone\n - 1F473 200D 2640 FE0F \; fully-qualified # 👳 - ‍♀️ E4.0 woman wearing turban\n1F473 200D 2640 - \; minimally-qualified # 👳‍♀ E4.0 woman wearing turban\n1F473 - 1F3FB 200D 2640 FE0F \; fully-qualified # 👳🏻‍ - ♀️ E4.0 woman wearing turban: light skin tone\n1F473 1F3FB 200D 2640 - \; minimally-qualified # 👳🏻‍♀ E4.0 woman wea - ring turban: light skin tone\n1F473 1F3FC 200D 2640 FE0F \ - ; fully-qualified # 👳🏼‍♀️ E4.0 woman wearing turban: mediu - m-light skin tone\n1F473 1F3FC 200D 2640 \; minimally - -qualified # 👳🏼‍♀ E4.0 woman wearing turban: medium-light skin t - one\n1F473 1F3FD 200D 2640 FE0F \; fully-qualified # - ��🏽‍♀️ E4.0 woman wearing turban: medium skin tone\n1F473 1F3 - FD 200D 2640 \; minimally-qualified # 👳🏽‍♀ - E4.0 woman wearing turban: medium skin tone\n1F473 1F3FE 200D 2640 FE0F - \; fully-qualified # 👳🏾‍♀️ E4.0 woman wearin - g turban: medium-dark skin tone\n1F473 1F3FE 200D 2640 - \; minimally-qualified # 👳🏾‍♀ E4.0 woman wearing turban: mediu - m-dark skin tone\n1F473 1F3FF 200D 2640 FE0F \; fully-qual - ified # 👳🏿‍♀️ E4.0 woman wearing turban: dark skin tone\n1 - F473 1F3FF 200D 2640 \; minimally-qualified # 👳 - 🏿‍♀ E4.0 woman wearing turban: dark skin tone\n1F472 - \; fully-qualified # 👲 E2.0 man with skullca - p\n1F472 1F3FB \; fully-qualified # - 👲🏻 E2.0 man with skullcap: light skin tone\n1F472 1F3FC - \; fully-qualified # 👲🏼 E2.0 man with skullca - p: medium-light skin tone\n1F472 1F3FD \; f - ully-qualified # 👲🏽 E2.0 man with skullcap: medium skin tone\n1F - 472 1F3FE \; fully-qualified # 👲🏾 - E2.0 man with skullcap: medium-dark skin tone\n1F472 1F3FF - \; fully-qualified # 👲🏿 E2.0 man with skullcap: - dark skin tone\n1F9D5 \; fully-quali - fied # 🧕 E5.0 woman with headscarf\n1F9D5 1F3FB - \; fully-qualified # 🧕🏻 E5.0 woman with headscarf: l - ight skin tone\n1F9D5 1F3FC \; fully-qualif - ied # 🧕🏼 E5.0 woman with headscarf: medium-light skin tone\n1F9D - 5 1F3FD \; fully-qualified # 🧕🏽 E - 5.0 woman with headscarf: medium skin tone\n1F9D5 1F3FE - \; fully-qualified # 🧕🏾 E5.0 woman with headscarf: - medium-dark skin tone\n1F9D5 1F3FF \; fully - -qualified # 🧕🏿 E5.0 woman with headscarf: dark skin tone\n1F935 - \; fully-qualified # 🤵 E4.0 m - an in tuxedo\n1F935 1F3FB \; fully-qualifie - d # 🤵🏻 E4.0 man in tuxedo: light skin tone\n1F935 1F3FC - \; fully-qualified # 🤵🏼 E4.0 man in tuxed - o: medium-light skin tone\n1F935 1F3FD \; f - ully-qualified # 🤵🏽 E4.0 man in tuxedo: medium skin tone\n1F935 - 1F3FE \; fully-qualified # 🤵🏾 E4. - 0 man in tuxedo: medium-dark skin tone\n1F935 1F3FF - \; fully-qualified # 🤵🏿 E4.0 man in tuxedo: dark skin t - one\n1F470 \; fully-qualified # - 👰 E2.0 bride with veil\n1F470 1F3FB \; f - ully-qualified # ��🏻 E2.0 bride with veil: light skin tone\n1F4 - 70 1F3FC \; fully-qualified # 👰🏼 - E2.0 bride with veil: medium-light skin tone\n1F470 1F3FD - \; fully-qualified # 👰🏽 E2.0 bride with veil: med - ium skin tone\n1F470 1F3FE \; fully-qualifi - ed # 👰🏾 E2.0 bride with veil: medium-dark skin tone\n1F470 1F3FF - \; fully-qualified # 👰🏿 E2.0 bri - de with veil: dark skin tone\n1F930 \ - ; fully-qualified # 🤰 E4.0 pregnant woman\n1F930 1F3FB - \; fully-qualified # 🤰🏻 E4.0 pregnant woman: - light skin tone\n1F930 1F3FC \; fully-quali - fied # 🤰🏼 E4.0 pregnant woman: medium-light skin tone\n1F930 1F3 - FD \; fully-qualified # 🤰🏽 E4.0 p - regnant woman: medium skin tone\n1F930 1F3FE - \; fully-qualified # 🤰🏾 E4.0 pregnant woman: medium-dark skin - tone\n1F930 1F3FF \; fully-qualified # - 🤰�� E4.0 pregnant woman: dark skin tone\n1F931 - \; fully-qualified # 🤱 E5.0 breast-feeding\n1F931 - 1F3FB \; fully-qualified # 🤱🏻 E5. - 0 breast-feeding: light skin tone\n1F931 1F3FC - \; fully-qualified # 🤱🏼 E5.0 breast-feeding: medium-light sk - in tone\n1F931 1F3FD \; fully-qualified - # 🤱🏽 E5.0 breast-feeding: medium skin tone\n1F931 1F3FE - \; fully-qualified # 🤱🏾 E5.0 breast-feeding: - medium-dark skin tone\n1F931 1F3FF \; full - y-qualified # 🤱🏿 E5.0 breast-feeding: dark skin tone\n\n# subgro - up: person-fantasy\n1F47C \; fully-qu - alified # 👼 E2.0 baby angel\n1F47C 1F3FB - \; fully-qualified # 👼🏻 E2.0 baby angel: light skin tone\n1 - F47C 1F3FC \; fully-qualified # 👼 - 🏼 E2.0 baby angel: medium-light skin tone\n1F47C 1F3FD - \; fully-qualified # 👼🏽 E2.0 baby angel: medium s - kin tone\n1F47C 1F3FE \; fully-qualified - # 👼🏾 E2.0 baby angel: medium-dark skin tone\n1F47C 1F3FF - \; fully-qualified # 👼🏿 E2.0 baby angel: d - ark skin tone\n1F385 \; fully-qualifi - ed # 🎅 E2.0 Santa Claus\n1F385 1F3FB - \; fully-qualified # 🎅🏻 E2.0 Santa Claus: light skin tone\n1F38 - 5 1F3FC \; fully-qualified # 🎅🏼 E - 2.0 Santa Claus: medium-light skin tone\n1F385 1F3FD - \; fully-qualified # 🎅🏽 E2.0 Santa Claus: medium skin - tone\n1F385 1F3FE \; fully-qualified # - 🎅🏾 E2.0 Santa Claus: medium-dark skin tone\n1F385 1F3FF - \; fully-qualified # 🎅🏿 E2.0 Santa Claus: dar - k skin tone\n1F936 \; fully-qualified - # 🤶 E4.0 Mrs. Claus\n1F936 1F3FB \; - fully-qualified # 🤶🏻 E4.0 Mrs. Claus: light skin tone\n1F936 1F - 3FC \; fully-qualified # 🤶🏼 E4.0 - Mrs. Claus: medium-light skin tone\n1F936 1F3FD - \; fully-qualified # 🤶🏽 E4.0 Mrs. Claus: medium skin tone\n - 1F936 1F3FE \; fully-qualified # 🤶 - 🏾 E4.0 Mrs. Claus: medium-dark skin tone\n1F936 1F3FF - \; fully-qualified # 🤶🏿 E4.0 Mrs. Claus: dark skin - tone\n1F9B8 \; fully-qualified # - 🦸 E11.0 superhero\n1F9B8 1F3FB \; fully - -qualified # 🦸🏻 E11.0 superhero: light skin tone\n1F9B8 1F3FC - \; fully-qualified # 🦸🏼 E11.0 super - hero: medium-light skin tone\n1F9B8 1F3FD \ - ; fully-qualified # 🦸🏽 E11.0 superhero: medium skin tone\n1F9B8 - 1F3FE \; fully-qualified # 🦸🏾 E11 - .0 superhero: medium-dark skin tone\n1F9B8 1F3FF - \; fully-qualified # 🦸🏿 E11.0 superhero: dark skin tone\n1 - F9B8 200D 2642 FE0F \; fully-qualified # 🦸‍ - ♂️ E11.0 man superhero\n1F9B8 200D 2642 \; - minimally-qualified # 🦸‍♂ E11.0 man superhero\n1F9B8 1F3FB 200D 264 - 2 FE0F \; fully-qualified # 🦸🏻‍♂️ E11.0 ma - n superhero: light skin tone\n1F9B8 1F3FB 200D 2642 \ - ; minimally-qualified # 🦸🏻‍♂ E11.0 man superhero: light skin ton - e\n1F9B8 1F3FC 200D 2642 FE0F \; fully-qualified # - 🦸🏼‍♂️ E11.0 man superhero: medium-light skin tone\n1F9B8 1F3FC - 200D 2642 \; minimally-qualified # 🦸🏼‍♂ E1 - 1.0 man superhero: medium-light skin tone\n1F9B8 1F3FD 200D 2642 FE0F - \; fully-qualified # 🦸🏽‍♂️ E11.0 man superhero - : medium skin tone\n1F9B8 1F3FD 200D 2642 \; minimall - y-qualified # 🦸🏽‍♂ E11.0 man superhero: medium skin tone\n1F9B8 - 1F3FE 200D 2642 FE0F \; fully-qualified # 🦸🏾‍ - ♂️ E11.0 man superhero: medium-dark skin tone\n1F9B8 1F3FE 200D 2642 - \; minimally-qualified # 🦸🏾‍♂ E11.0 man supe - rhero: medium-dark skin tone\n1F9B8 1F3FF 200D 2642 FE0F \ - ; fully-qualified # 🦸🏿‍♂️ E11.0 man superhero: dark skin t - one\n1F9B8 1F3FF 200D 2642 \; minimally-qualified # - 🦸🏿‍♂ E11.0 man superhero: dark skin tone\n1F9B8 200D 2640 FE0F - \; fully-qualified # 🦸‍♀️ E11.0 woman su - perhero\n1F9B8 200D 2640 \; minimally-qualified - # 🦸‍♀ E11.0 woman superhero\n1F9B8 1F3FB 200D 2640 FE0F - \; fully-qualified # 🦸🏻‍♀️ E11.0 woman superhero: li - ght skin tone\n1F9B8 1F3FB 200D 2640 \; minimally-qua - lified # 🦸🏻‍♀ E11.0 woman superhero: light skin tone\n1F9B8 1F3F - C 200D 2640 FE0F \; fully-qualified # 🦸🏼‍♀ - ️ E11.0 woman superhero: medium-light skin tone\n1F9B8 1F3FC 200D 2640 - \; minimally-qualified # 🦸🏼‍♀ E11.0 woman su - perhero: medium-light skin tone\n1F9B8 1F3FD 200D 2640 FE0F - \; fully-qualified # 🦸🏽‍♀️ E11.0 woman superhero: medium - skin tone\n1F9B8 1F3FD 200D 2640 \; minimally-qualif - ied # 🦸🏽‍♀ E11.0 woman superhero: medium skin tone\n1F9B8 1F3FE - 200D 2640 FE0F \; fully-qualified # 🦸🏾‍♀️ - E11.0 woman superhero: medium-dark skin tone\n1F9B8 1F3FE 200D 2640 - \; minimally-qualified # 🦸🏾‍♀ E11.0 woman superhe - ro: medium-dark skin tone\n1F9B8 1F3FF 200D 2640 FE0F \; f - ully-qualified # 🦸🏿‍♀️ E11.0 woman superhero: dark skin to - ne\n1F9B8 1F3FF 200D 2640 \; minimally-qualified # - 🦸🏿‍♀ E11.0 woman superhero: dark skin tone\n1F9B9 - \; fully-qualified # 🦹 E11.0 supervillain\n1 - F9B9 1F3FB \; fully-qualified # 🦹 - 🏻 E11.0 supervillain: light skin tone\n1F9B9 1F3FC - \; fully-qualified # 🦹🏼 E11.0 supervillain: medium-li - ght skin tone\n1F9B9 1F3FD \; fully-qualifi - ed # 🦹🏽 E11.0 supervillain: medium skin tone\n1F9B9 1F3FE - \; fully-qualified # 🦹🏾 E11.0 supervill - ain: medium-dark skin tone\n1F9B9 1F3FF \; - fully-qualified # 🦹🏿 E11.0 supervillain: dark skin tone\n1F9B9 2 - 00D 2642 FE0F \; fully-qualified # 🦹‍♂️ - E11.0 man supervillain\n1F9B9 200D 2642 \; min - imally-qualified # 🦹‍♂ E11.0 man supervillain\n1F9B9 1F3FB 200D 264 - 2 FE0F \; fully-qualified # 🦹🏻‍♂️ E11.0 ma - n supervillain: light skin tone\n1F9B9 1F3FB 200D 2642 - \; minimally-qualified # 🦹🏻‍♂ E11.0 man supervillain: light sk - in tone\n1F9B9 1F3FC 200D 2642 FE0F \; fully-qualified - # 🦹🏼‍♂️ E11.0 man supervillain: medium-light skin tone\n1F9B9 - 1F3FC 200D 2642 \; minimally-qualified # 🦹🏼‍ - ♂ E11.0 man supervillain: medium-light skin tone\n1F9B9 1F3FD 200D 2642 - FE0F \; fully-qualified # 🦹🏽‍♂️ E11.0 man - supervillain: medium skin tone\n1F9B9 1F3FD 200D 2642 - \; minimally-qualified # 🦹🏽‍♂ E11.0 man supervillain: medium sk - in tone\n1F9B9 1F3FE 200D 2642 FE0F \; fully-qualified - # 🦹🏾‍♂️ E11.0 man supervillain: medium-dark skin tone\n1F9B9 - 1F3FE 200D 2642 \; minimally-qualified # 🦹🏾‍ - ♂ E11.0 man supervillain: medium-dark skin tone\n1F9B9 1F3FF 200D 2642 F - E0F \; fully-qualified # 🦹🏿‍♂️ E11.0 man s - upervillain: dark skin tone\n1F9B9 1F3FF 200D 2642 \; - minimally-qualified # 🦹🏿‍♂ E11.0 man supervillain: dark skin to - ne\n1F9B9 200D 2640 FE0F \; fully-qualified # - 🦹‍♀️ E11.0 woman supervillain\n1F9B9 200D 2640 - \; minimally-qualified # 🦹‍♀ E11.0 woman supervillain\n1F9 - B9 1F3FB 200D 2640 FE0F \; fully-qualified # 🦹🏻 - ‍♀️ E11.0 woman supervillain: light skin tone\n1F9B9 1F3FB 200D 2640 - \; minimally-qualified # 🦹🏻‍♀ E11.0 woman - supervillain: light skin tone\n1F9B9 1F3FC 200D 2640 FE0F - \; fully-qualified # 🦹��‍♀️ E11.0 woman supervillain: med - ium-light skin tone\n1F9B9 1F3FC 200D 2640 \; minimal - ly-qualified # 🦹🏼‍♀ E11.0 woman supervillain: medium-light skin - tone\n1F9B9 1F3FD 200D 2640 FE0F \; fully-qualified # - 🦹🏽‍♀️ E11.0 woman supervillain: medium skin tone\n1F9B9 1F3FD - 200D 2640 \; minimally-qualified # 🦹🏽‍♀ E11 - .0 woman supervillain: medium skin tone\n1F9B9 1F3FE 200D 2640 FE0F - \; fully-qualified # 🦹🏾‍♀️ E11.0 woman supervill - ain: medium-dark skin tone\n1F9B9 1F3FE 200D 2640 \; - minimally-qualified # 🦹🏾‍♀ E11.0 woman supervillain: medium-dark - skin tone\n1F9B9 1F3FF 200D 2640 FE0F \; fully-qualified - # 🦹🏿‍♀️ E11.0 woman supervillain: dark skin tone\n1F9B9 1F - 3FF 200D 2640 \; minimally-qualified # 🦹🏿‍♀ - E11.0 woman supervillain: dark skin tone\n1F9D9 - \; fully-qualified # 🧙 E5.0 mage\n1F9D9 1F3FB - \; fully-qualified # 🧙🏻 E5.0 mage: light s - kin tone\n1F9D9 1F3FC \; fully-qualified - # 🧙🏼 E5.0 mage: medium-light skin tone\n1F9D9 1F3FD - \; fully-qualified # 🧙🏽 E5.0 mage: medium skin - tone\n1F9D9 1F3FE \; fully-qualified # - 🧙🏾 E5.0 mage: medium-dark skin tone\n1F9D9 1F3FF - \; fully-qualified # 🧙🏿 E5.0 mage: dark skin tone\n1 - F9D9 200D 2642 FE0F \; fully-qualified # 🧙‍ - ♂️ E5.0 man mage\n1F9D9 200D 2642 \; minima - lly-qualified # 🧙‍♂ E5.0 man mage\n1F9D9 1F3FB 200D 2642 FE0F - \; fully-qualified # 🧙🏻‍♂️ E5.0 man mage: light - skin tone\n1F9D9 1F3FB 200D 2642 \; minimally-qualif - ied # 🧙🏻‍♂ E5.0 man mage: light skin tone\n1F9D9 1F3FC 200D 2642 - FE0F \; fully-qualified # 🧙🏼‍♂️ E5.0 man - mage: medium-light skin tone\n1F9D9 1F3FC 200D 2642 \ - ; minimally-qualified # 🧙🏼‍♂ E5.0 man mage: medium-light skin to - ne\n1F9D9 1F3FD 200D 2642 FE0F \; fully-qualified # - 🧙🏽‍♂️ E5.0 man mage: medium skin tone\n1F9D9 1F3FD 200D 2642 - \; minimally-qualified # 🧙🏽‍♂ E5.0 man mage: - medium skin tone\n1F9D9 1F3FE 200D 2642 FE0F \; fully-qua - lified # 🧙🏾‍♂️ E5.0 man mage: medium-dark skin tone\n1F9D9 - 1F3FE 200D 2642 \; minimally-qualified # 🧙🏾‍ - ♂ E5.0 man mage: medium-dark skin tone\n1F9D9 1F3FF 200D 2642 FE0F - \; fully-qualified # 🧙🏿‍♂️ E5.0 man mage: dark - skin tone\n1F9D9 1F3FF 200D 2642 \; minimally-qualifi - ed # 🧙🏿‍♂ E5.0 man mage: dark skin tone\n1F9D9 200D 2640 FE0F - \; fully-qualified # ��‍♀️ E5.0 woman ma - ge\n1F9D9 200D 2640 \; minimally-qualified # - 🧙‍♀ E5.0 woman mage\n1F9D9 1F3FB 200D 2640 FE0F \; - fully-qualified # 🧙🏻‍♀️ E5.0 woman mage: light skin tone\n - 1F9D9 1F3FB 200D 2640 \; minimally-qualified # 🧙 - 🏻‍♀ E5.0 woman mage: light skin tone\n1F9D9 1F3FC 200D 2640 FE0F - \; fully-qualified # 🧙🏼‍♀️ E5.0 woman mage: - medium-light skin tone\n1F9D9 1F3FC 200D 2640 \; mini - mally-qualified # 🧙🏼‍♀ E5.0 woman mage: medium-light skin tone\n - 1F9D9 1F3FD 200D 2640 FE0F \; fully-qualified # 🧙 - 🏽‍♀️ E5.0 woman mage: medium skin tone\n1F9D9 1F3FD 200D 2640 - \; minimally-qualified # 🧙🏽‍♀ E5.0 woman mage: - medium skin tone\n1F9D9 1F3FE 200D 2640 FE0F \; fully-qua - lified # 🧙🏾‍♀️ E5.0 woman mage: medium-dark skin tone\n1F9 - D9 1F3FE 200D 2640 \; minimally-qualified # 🧙🏾 - ‍♀ E5.0 woman mage: medium-dark skin tone\n1F9D9 1F3FF 200D 2640 FE0F - \; fully-qualified # 🧙🏿‍♀️ E5.0 woman mage - : dark skin tone\n1F9D9 1F3FF 200D 2640 \; minimally- - qualified # 🧙🏿‍♀ E5.0 woman mage: dark skin tone\n1F9DA - \; fully-qualified # 🧚 E5.0 fairy\n1F9 - DA 1F3FB \; fully-qualified # 🧚🏻 - E5.0 fairy: light skin tone\n1F9DA 1F3FC \; - fully-qualified # 🧚🏼 E5.0 fairy: medium-light skin tone\n1F9DA - 1F3FD \; fully-qualified # 🧚🏽 E5. - 0 fairy: medium skin tone\n1F9DA 1F3FE \; f - ully-qualified # 🧚🏾 E5.0 fairy: medium-dark skin tone\n1F9DA 1F3 - FF \; fully-qualified # 🧚🏿 E5.0 f - airy: dark skin tone\n1F9DA 200D 2642 FE0F \; fully- - qualified # 🧚‍♂️ E5.0 man fairy\n1F9DA 200D 2642 - \; minimally-qualified # 🧚‍♂ E5.0 man fairy\n1F9DA 1 - F3FB 200D 2642 FE0F \; fully-qualified # 🧚🏻‍ - ♂️ E5.0 man fairy: light skin tone\n1F9DA 1F3FB 200D 2642 - \; minimally-qualified # 🧚🏻‍♂ E5.0 man fairy: light ski - n tone\n1F9DA 1F3FC 200D 2642 FE0F \; fully-qualified - # 🧚🏼‍♂️ E5.0 man fairy: medium-light skin tone\n1F9DA 1F3FC 20 - 0D 2642 \; minimally-qualified # 🧚🏼‍♂ E5.0 - man fairy: medium-light skin tone\n1F9DA 1F3FD 200D 2642 FE0F - \; fully-qualified # 🧚🏽‍♂️ E5.0 man fairy: medium skin - tone\n1F9DA 1F3FD 200D 2642 \; minimally-qualified # - 🧚🏽‍♂ E5.0 man fairy: medium skin tone\n1F9DA 1F3FE 200D 2642 FE - 0F \; fully-qualified # 🧚🏾‍♂️ E5.0 man fai - ry: medium-dark skin tone\n1F9DA 1F3FE 200D 2642 \; m - inimally-qualified # 🧚🏾‍♂ E5.0 man fairy: medium-dark skin tone\ - n1F9DA 1F3FF 200D 2642 FE0F \; fully-qualified # 🧚 - 🏿‍♂️ E5.0 man fairy: dark skin tone\n1F9DA 1F3FF 200D 2642 - \; minimally-qualified # 🧚🏿‍♂ E5.0 man fairy: dar - k skin tone\n1F9DA 200D 2640 FE0F \; fully-qualified - # 🧚‍♀️ E5.0 woman fairy\n1F9DA 200D 2640 - \; minimally-qualified # 🧚‍♀ E5.0 woman fairy\n1F9DA 1F3FB - 200D 2640 FE0F \; fully-qualified # 🧚🏻‍♀️ - E5.0 woman fairy: light skin tone\n1F9DA 1F3FB 200D 2640 - \; minimally-qualified # 🧚🏻‍♀ E5.0 woman fairy: light skin t - one\n1F9DA 1F3FC 200D 2640 FE0F \; fully-qualified # - 🧚🏼‍♀️ E5.0 woman fairy: medium-light skin tone\n1F9DA 1F3FC 20 - 0D 2640 \; minimally-qualified # 🧚🏼‍♀ E5.0 - woman fairy: medium-light skin tone\n1F9DA 1F3FD 200D 2640 FE0F - \; fully-qualified # 🧚🏽‍♀️ E5.0 woman fairy: medium - skin tone\n1F9DA 1F3FD 200D 2640 \; minimally-qualifi - ed # 🧚🏽‍♀ E5.0 woman fairy: medium skin tone\n1F9DA 1F3FE 200D 2 - 640 FE0F \; fully-qualified # 🧚🏾‍♀️ E5.0 w - oman fairy: medium-dark skin tone\n1F9DA 1F3FE 200D 2640 - \; minimally-qualified # 🧚🏾‍♀ E5.0 woman fairy: medium-dark - skin tone\n1F9DA 1F3FF 200D 2640 FE0F \; fully-qualified - # 🧚🏿‍♀️ E5.0 woman fairy: dark skin tone\n1F9DA 1F3FF 200D - 2640 \; minimally-qualified # 🧚🏿‍♀ E5.0 wom - an fairy: dark skin tone\n1F9DB \; fu - lly-qualified # 🧛 E5.0 vampire\n1F9DB 1F3FB - \; fully-qualified # 🧛🏻 E5.0 vampire: light skin tone\n1 - F9DB 1F3FC \; fully-qualified # 🧛 - 🏼 E5.0 vampire: medium-light skin tone\n1F9DB 1F3FD - \; fully-qualified # 🧛🏽 E5.0 vampire: medium skin to - ne\n1F9DB 1F3FE \; fully-qualified # - 🧛🏾 E5.0 vampire: medium-dark skin tone\n1F9DB 1F3FF - \; fully-qualified # 🧛🏿 E5.0 vampire: dark skin t - one\n1F9DB 200D 2642 FE0F \; fully-qualified # - 🧛‍♂️ E5.0 man vampire\n1F9DB 200D 2642 - \; minimally-qualified # 🧛‍♂ E5.0 man vampire\n1F9DB 1F3FB 200D 26 - 42 FE0F \; fully-qualified # 🧛🏻‍♂️ E5.0 ma - n vampire: light skin tone\n1F9DB 1F3FB 200D 2642 \; - minimally-qualified # 🧛🏻‍♂ E5.0 man vampire: light skin tone\n1F - 9DB 1F3FC 200D 2642 FE0F \; fully-qualified # 🧛🏼 - ‍♂️ E5.0 man vampire: medium-light skin tone\n1F9DB 1F3FC 200D 2642 - \; minimally-qualified # 🧛🏼‍♂ E5.0 man vamp - ire: medium-light skin tone\n1F9DB 1F3FD 200D 2642 FE0F \; - fully-qualified # 🧛🏽‍♂️ E5.0 man vampire: medium skin ton - e\n1F9DB 1F3FD 200D 2642 \; minimally-qualified # - 🧛🏽‍♂ E5.0 man vampire: medium skin tone\n1F9DB 1F3FE 200D 2642 F - E0F \; fully-qualified # 🧛🏾‍♂️ E5.0 man va - mpire: medium-dark skin tone\n1F9DB 1F3FE 200D 2642 \ - ; minimally-qualified # 🧛🏾‍♂ E5.0 man vampire: medium-dark skin - tone\n1F9DB 1F3FF 200D 2642 FE0F \; fully-qualified # - ��🏿‍♂️ E5.0 man vampire: dark skin tone\n1F9DB 1F3FF 200D 264 - 2 \; minimally-qualified # 🧛🏿‍♂ E5.0 man va - mpire: dark skin tone\n1F9DB 200D 2640 FE0F \; fully - -qualified # 🧛‍♀️ E5.0 woman vampire\n1F9DB 200D 2640 - \; minimally-qualified # 🧛‍♀ E5.0 woman vampire - \n1F9DB 1F3FB 200D 2640 FE0F \; fully-qualified # 🧛 - 🏻‍♀️ E5.0 woman vampire: light skin tone\n1F9DB 1F3FB 200D 2640 - \; minimally-qualified # 🧛🏻‍♀ E5.0 woman vam - pire: light skin tone\n1F9DB 1F3FC 200D 2640 FE0F \; fully - -qualified # 🧛🏼‍♀️ E5.0 woman vampire: medium-light skin t - one\n1F9DB 1F3FC 200D 2640 \; minimally-qualified # - 🧛🏼‍♀ E5.0 woman vampire: medium-light skin tone\n1F9DB 1F3FD 200 - D 2640 FE0F \; fully-qualified # 🧛🏽‍♀️ E5. - 0 woman vampire: medium skin tone\n1F9DB 1F3FD 200D 2640 - \; minimally-qualified # 🧛🏽‍♀ E5.0 woman vampire: medium ski - n tone\n1F9DB 1F3FE 200D 2640 FE0F \; fully-qualified - # 🧛🏾‍♀️ E5.0 woman vampire: medium-dark skin tone\n1F9DB 1F3FE - 200D 2640 \; minimally-qualified # 🧛🏾‍♀ E5 - .0 woman vampire: medium-dark skin tone\n1F9DB 1F3FF 200D 2640 FE0F - \; fully-qualified # 🧛🏿‍♀️ E5.0 woman vampire: d - ark skin tone\n1F9DB 1F3FF 200D 2640 \; minimally-qua - lified # 🧛🏿‍♀ E5.0 woman vampire: dark skin tone\n1F9DC - \; fully-qualified # 🧜 E5.0 merperson\ - n1F9DC 1F3FB \; fully-qualified # 🧜 - 🏻 E5.0 merperson: light skin tone\n1F9DC 1F3FC - \; fully-qualified # 🧜🏼 E5.0 merperson: medium-light skin - tone\n1F9DC 1F3FD \; fully-qualified # - 🧜🏽 E5.0 merperson: medium skin tone\n1F9DC 1F3FE - \; fully-qualified # 🧜🏾 E5.0 merperson: medium-dark - skin tone\n1F9DC 1F3FF \; fully-qualified - # 🧜🏿 E5.0 merperson: dark skin tone\n1F9DC 200D 2642 FE0F - \; fully-qualified # 🧜‍♂️ E5.0 merman\n1F9DC - 200D 2642 \; minimally-qualified # 🧜‍♂ E - 5.0 merman\n1F9DC 1F3FB 200D 2642 FE0F \; fully-qualified - # 🧜🏻‍♂️ E5.0 merman: light skin tone\n1F9DC 1F3FB 200D 264 - 2 \; minimally-qualified # 🧜🏻‍♂ E5.0 merman - : light skin tone\n1F9DC 1F3FC 200D 2642 FE0F \; fully-qua - lified # 🧜🏼‍♂️ E5.0 merman: medium-light skin tone\n1F9DC - 1F3FC 200D 2642 \; minimally-qualified # 🧜🏼‍ - ♂ E5.0 merman: medium-light skin tone\n1F9DC 1F3FD 200D 2642 FE0F - \; fully-qualified # 🧜🏽‍♂️ E5.0 merman: medium s - kin tone\n1F9DC 1F3FD 200D 2642 \; minimally-qualifie - d # 🧜🏽‍♂ E5.0 merman: medium skin tone\n1F9DC 1F3FE 200D 2642 FE - 0F \; fully-qualified # 🧜🏾‍♂️ E5.0 merman: - medium-dark skin tone\n1F9DC 1F3FE 200D 2642 \; mini - mally-qualified # 🧜🏾‍♂ E5.0 merman: medium-dark skin tone\n1F9DC - 1F3FF 200D 2642 FE0F \; fully-qualified # 🧜🏿‍ - ♂️ E5.0 merman: dark skin tone\n1F9DC 1F3FF 200D 2642 - \; minimally-qualified # 🧜🏿‍♂ E5.0 merman: dark skin tone\n - 1F9DC 200D 2640 FE0F \; fully-qualified # 🧜 - ‍♀️ E5.0 mermaid\n1F9DC 200D 2640 \; mini - mally-qualified # 🧜‍♀ E5.0 mermaid\n1F9DC 1F3FB 200D 2640 FE0F - \; fully-qualified # 🧜🏻‍♀️ E5.0 mermaid: light - skin tone\n1F9DC 1F3FB 200D 2640 \; minimally-qualif - ied # 🧜🏻‍♀ E5.0 mermaid: light skin tone\n1F9DC 1F3FC 200D 2640 - FE0F \; fully-qualified # 🧜🏼‍♀️ E5.0 merma - id: medium-light skin tone\n1F9DC 1F3FC 200D 2640 \; - minimally-qualified # 🧜🏼‍♀ E5.0 mermaid: medium-light skin tone\ - n1F9DC 1F3FD 200D 2640 FE0F \; fully-qualified # 🧜 - 🏽‍♀️ E5.0 mermaid: medium skin tone\n1F9DC 1F3FD 200D 2640 - \; minimally-qualified # 🧜🏽‍♀ E5.0 mermaid: mediu - m skin tone\n1F9DC 1F3FE 200D 2640 FE0F \; fully-qualified - # 🧜🏾‍♀️ E5.0 mermaid: medium-dark skin tone\n1F9DC 1F3FE - 200D 2640 \; minimally-qualified # 🧜🏾‍♀ E5. - 0 mermaid: medium-dark skin tone\n1F9DC 1F3FF 200D 2640 FE0F - \; fully-qualified # 🧜🏿‍♀️ E5.0 mermaid: dark skin tone - \n1F9DC 1F3FF 200D 2640 \; minimally-qualified # 🧜 - 🏿‍♀ E5.0 mermaid: dark skin tone\n1F9DD - \; fully-qualified # 🧝 E5.0 elf\n1F9DD 1F3FB - \; fully-qualified # 🧝🏻 E5.0 elf: light skin - tone\n1F9DD 1F3FC \; fully-qualified # - 🧝🏼 E5.0 elf: medium-light skin tone\n1F9DD 1F3FD - \; fully-qualified # 🧝🏽 E5.0 elf: medium skin tone\n - 1F9DD 1F3FE \; fully-qualified # 🧝 - 🏾 E5.0 elf: medium-dark skin tone\n1F9DD 1F3FF - \; fully-qualified # 🧝🏿 E5.0 elf: dark skin tone\n1F9DD 2 - 00D 2642 FE0F \; fully-qualified # 🧝‍♂️ - E5.0 man elf\n1F9DD 200D 2642 \; minimally-qua - lified # 🧝‍♂ E5.0 man elf\n1F9DD 1F3FB 200D 2642 FE0F - \; fully-qualified # 🧝🏻‍♂️ E5.0 man elf: light skin ton - e\n1F9DD 1F3FB 200D 2642 \; minimally-qualified # - 🧝🏻‍♂ E5.0 man elf: light skin tone\n1F9DD 1F3FC 200D 2642 FE0F - \; fully-qualified # 🧝🏼‍♂️ E5.0 man elf: me - dium-light skin tone\n1F9DD 1F3FC 200D 2642 \; minima - lly-qualified # 🧝🏼‍♂ E5.0 man elf: medium-light skin tone\n1F9DD - 1F3FD 200D 2642 FE0F \; fully-qualified # 🧝🏽‍ - ♂️ E5.0 man elf: medium skin tone\n1F9DD 1F3FD 200D 2642 - \; minimally-qualified # 🧝🏽‍♂ E5.0 man elf: medium skin - tone\n1F9DD 1F3FE 200D 2642 FE0F \; fully-qualified # - 🧝🏾‍♂️ E5.0 man elf: medium-dark skin tone\n1F9DD 1F3FE 200D 26 - 42 \; minimally-qualified # 🧝🏾‍♂ E5.0 man e - lf: medium-dark skin tone\n1F9DD 1F3FF 200D 2642 FE0F \; f - ully-qualified # 🧝🏿‍♂️ E5.0 man elf: dark skin tone\n1F9DD - 1F3FF 200D 2642 \; minimally-qualified # 🧝🏿‍ - ♂ E5.0 man elf: dark skin tone\n1F9DD 200D 2640 FE0F - \; fully-qualified # 🧝‍♀️ E5.0 woman elf\n1F9DD 200D 2640 - \; minimally-qualified # 🧝‍♀ E5.0 woman - elf\n1F9DD 1F3FB 200D 2640 FE0F \; fully-qualified # - 🧝🏻‍♀️ E5.0 woman elf: light skin tone\n1F9DD 1F3FB 200D 2640 - \; minimally-qualified # 🧝🏻‍♀ E5.0 woman elf - : light skin tone\n1F9DD 1F3FC 200D 2640 FE0F \; fully-qua - lified # 🧝🏼‍♀️ E5.0 woman elf: medium-light skin tone\n1F9 - DD 1F3FC 200D 2640 \; minimally-qualified # 🧝🏼 - ‍♀ E5.0 woman elf: medium-light skin tone\n1F9DD 1F3FD 200D 2640 FE0F - \; fully-qualified # 🧝🏽‍♀️ E5.0 woman elf: - medium skin tone\n1F9DD 1F3FD 200D 2640 \; minimally - -qualified # 🧝🏽‍♀ E5.0 woman elf: medium skin tone\n1F9DD 1F3FE - 200D 2640 FE0F \; fully-qualified # 🧝🏾‍♀️ - E5.0 woman elf: medium-dark skin tone\n1F9DD 1F3FE 200D 2640 - \; minimally-qualified # 🧝🏾‍♀ E5.0 woman elf: medium-dar - k skin tone\n1F9DD 1F3FF 200D 2640 FE0F \; fully-qualified - # 🧝🏿‍♀️ E5.0 woman elf: dark skin tone\n1F9DD 1F3FF 200D - 2640 \; minimally-qualified # 🧝🏿‍♀ E5.0 wom - an elf: dark skin tone\n1F9DE \; full - y-qualified # 🧞 E5.0 genie\n1F9DE 200D 2642 FE0F - \; fully-qualified # 🧞‍♂️ E5.0 man genie\n1F9DE 200D 2642 - \; minimally-qualified # 🧞‍♂ E5.0 man g - enie\n1F9DE 200D 2640 FE0F \; fully-qualified # - 🧞‍♀️ E5.0 woman genie\n1F9DE 200D 2640 - \; minimally-qualified # 🧞‍♀ E5.0 woman genie\n1F9DF - \; fully-qualified # 🧟 E5.0 zombie\n1F9DF 2 - 00D 2642 FE0F \; fully-qualified # 🧟‍♂️ - E5.0 man zombie\n1F9DF 200D 2642 \; minimally- - qualified # 🧟‍♂ E5.0 man zombie\n1F9DF 200D 2640 FE0F - \; fully-qualified # 🧟‍♀️ E5.0 woman zombie\n1F9DF 2 - 00D 2640 \; minimally-qualified # 🧟‍♀ E5 - .0 woman zombie\n\n# subgroup: person-activity\n1F486 - \; fully-qualified # 💆 E2.0 person getting massage - \n1F486 1F3FB \; fully-qualified # 💆 - 🏻 E2.0 person getting massage: light skin tone\n1F486 1F3FC - \; fully-qualified # 💆🏼 E2.0 person getting - massage: medium-light skin tone\n1F486 1F3FD - \; fully-qualified # 💆🏽 E2.0 person getting massage: medium sk - in tone\n1F486 1F3FE \; fully-qualified - # 💆🏾 E2.0 person getting massage: medium-dark skin tone\n1F486 1F3F - F \; fully-qualified # 💆�� E2.0 - person getting massage: dark skin tone\n1F486 200D 2642 FE0F - \; fully-qualified # 💆‍♂️ E4.0 man getting massage\n - 1F486 200D 2642 \; minimally-qualified # 💆 - ‍♂ E4.0 man getting massage\n1F486 1F3FB 200D 2642 FE0F - \; fully-qualified # 💆🏻‍♂️ E4.0 man getting massage: lig - ht skin tone\n1F486 1F3FB 200D 2642 \; minimally-qual - ified # 💆🏻‍♂ E4.0 man getting massage: light skin tone\n1F486 1F - 3FC 200D 2642 FE0F \; fully-qualified # 💆🏼‍♂ - ️ E4.0 man getting massage: medium-light skin tone\n1F486 1F3FC 200D 264 - 2 \; minimally-qualified # 💆🏼‍♂ E4.0 man ge - tting massage: medium-light skin tone\n1F486 1F3FD 200D 2642 FE0F - \; fully-qualified # 💆🏽‍♂️ E4.0 man getting massag - e: medium skin tone\n1F486 1F3FD 200D 2642 \; minimal - ly-qualified # 💆🏽‍♂ E4.0 man getting massage: medium skin tone\n - 1F486 1F3FE 200D 2642 FE0F \; fully-qualified # 💆 - 🏾‍♂️ E4.0 man getting massage: medium-dark skin tone\n1F486 1F3FE - 200D 2642 \; minimally-qualified # 💆🏾‍♂ E4 - .0 man getting massage: medium-dark skin tone\n1F486 1F3FF 200D 2642 FE0F - \; fully-qualified # 💆🏿‍♂️ E4.0 man gettin - g massage: dark skin tone\n1F486 1F3FF 200D 2642 \; m - inimally-qualified # 💆🏿‍♂ E4.0 man getting massage: dark skin to - ne\n1F486 200D 2640 FE0F \; fully-qualified # - 💆‍♀️ E4.0 woman getting massage\n1F486 200D 2640 - \; minimally-qualified # 💆‍♀ E4.0 woman getting massage\ - n1F486 1F3FB 200D 2640 FE0F \; fully-qualified # 💆 - 🏻‍♀️ E4.0 woman getting massage: light skin tone\n1F486 1F3FB 200 - D 2640 \; minimally-qualified # 💆🏻‍♀ E4.0 w - oman getting massage: light skin tone\n1F486 1F3FC 200D 2640 FE0F - \; fully-qualified # 💆🏼‍♀️ E4.0 woman getting mass - age: medium-light skin tone\n1F486 1F3FC 200D 2640 \; - minimally-qualified # 💆��‍♀ E4.0 woman getting massage: medium - -light skin tone\n1F486 1F3FD 200D 2640 FE0F \; fully-qual - ified # 💆🏽‍♀️ E4.0 woman getting massage: medium skin tone - \n1F486 1F3FD 200D 2640 \; minimally-qualified # 💆 - 🏽‍♀ E4.0 woman getting massage: medium skin tone\n1F486 1F3FE 200D - 2640 FE0F \; fully-qualified # 💆🏾‍♀️ E4.0 - woman getting massage: medium-dark skin tone\n1F486 1F3FE 200D 2640 - \; minimally-qualified # 💆🏾‍♀ E4.0 woman getting - massage: medium-dark skin tone\n1F486 1F3FF 200D 2640 FE0F - \; fully-qualified # 💆🏿‍♀️ E4.0 woman getting massage: da - rk skin tone\n1F486 1F3FF 200D 2640 \; minimally-qual - ified # 💆🏿‍♀ E4.0 woman getting massage: dark skin tone\n1F487 - \; fully-qualified # 💇 E2.0 per - son getting haircut\n1F487 1F3FB \; fully-q - ualified # 💇🏻 E2.0 person getting haircut: light skin tone\n1F48 - 7 1F3FC \; fully-qualified # 💇🏼 E - 2.0 person getting haircut: medium-light skin tone\n1F487 1F3FD - \; fully-qualified # 💇🏽 E2.0 person getting - haircut: medium skin tone\n1F487 1F3FE \; - fully-qualified # 💇🏾 E2.0 person getting haircut: medium-dark sk - in tone\n1F487 1F3FF \; fully-qualified - # 💇🏿 E2.0 person getting haircut: dark skin tone\n1F487 200D 2642 F - E0F \; fully-qualified # 💇‍♂️ E4.0 man - getting haircut\n1F487 200D 2642 \; minimally-q - ualified # 💇‍♂ E4.0 man getting haircut\n1F487 1F3FB 200D 2642 FE0F - \; fully-qualified # 💇🏻‍♂️ E4.0 man getti - ng haircut: light skin tone\n1F487 1F3FB 200D 2642 \; - minimally-qualified # 💇🏻‍♂ E4.0 man getting haircut: light skin - tone\n1F487 1F3FC 200D 2642 FE0F \; fully-qualified # - 💇🏼‍♂️ E4.0 man getting haircut: medium-light skin tone\n1F487 - 1F3FC 200D 2642 \; minimally-qualified # 💇🏼‍ - ♂ E4.0 man getting haircut: medium-light skin tone\n1F487 1F3FD 200D 264 - 2 FE0F \; fully-qualified # 💇🏽‍♂️ E4.0 man - getting haircut: medium skin tone\n1F487 1F3FD 200D 2642 - \; minimally-qualified # 💇🏽‍♂ E4.0 man getting haircut: med - ium skin tone\n1F487 1F3FE 200D 2642 FE0F \; fully-qualifi - ed # 💇🏾‍♂️ E4.0 man getting haircut: medium-dark skin tone - \n1F487 1F3FE 200D 2642 \; minimally-qualified # 💇 - 🏾‍♂ E4.0 man getting haircut: medium-dark skin tone\n1F487 1F3FF 20 - 0D 2642 FE0F \; fully-qualified # 💇🏿‍♂️ E4 - .0 man getting haircut: dark skin tone\n1F487 1F3FF 200D 2642 - \; minimally-qualified # 💇🏿‍♂ E4.0 man getting haircut: - dark skin tone\n1F487 200D 2640 FE0F \; fully-quali - fied # 💇‍♀️ E4.0 woman getting haircut\n1F487 200D 2640 - \; minimally-qualified # 💇‍♀ E4.0 woman getti - ng haircut\n1F487 1F3FB 200D 2640 FE0F \; fully-qualified - # 💇🏻‍♀️ E4.0 woman getting haircut: light skin tone\n1F487 - 1F3FB 200D 2640 \; minimally-qualified # 💇🏻‍ - ♀ E4.0 woman getting haircut: light skin tone\n1F487 1F3FC 200D 2640 FE0 - F \; fully-qualified # 💇🏼‍♀️ E4.0 woman ge - tting haircut: medium-light skin tone\n1F487 1F3FC 200D 2640 - \; minimally-qualified # 💇🏼‍♀ E4.0 woman getting haircut - : medium-light skin tone\n1F487 1F3FD 200D 2640 FE0F \; fu - lly-qualified # 💇🏽‍♀️ E4.0 woman getting haircut: medium s - kin tone\n1F487 1F3FD 200D 2640 \; minimally-qualifie - d # 💇🏽‍♀ E4.0 woman getting haircut: medium skin tone\n1F487 1F3 - FE 200D 2640 FE0F \; fully-qualified # 💇🏾‍♀ - ️ E4.0 woman getting haircut: medium-dark skin tone\n1F487 1F3FE 200D 26 - 40 \; minimally-qualified # 💇🏾‍♀ E4.0 woman - getting haircut: medium-dark skin tone\n1F487 1F3FF 200D 2640 FE0F - \; fully-qualified # 💇🏿‍♀️ E4.0 woman getting ha - ircut: dark skin tone\n1F487 1F3FF 200D 2640 \; minim - ally-qualified # 💇🏿‍♀ E4.0 woman getting haircut: dark skin tone - \n1F6B6 \; fully-qualified # 🚶 - E2.0 person walking\n1F6B6 1F3FB \; fully- - qualified # 🚶🏻 E2.0 person walking: light skin tone\n1F6B6 1F3FC - \; fully-qualified # 🚶🏼 E2.0 per - son walking: medium-light skin tone\n1F6B6 1F3FD - \; fully-qualified # 🚶🏽 E2.0 person walking: medium skin t - one\n1F6B6 1F3FE \; fully-qualified # - 🚶🏾 E2.0 person walking: medium-dark skin tone\n1F6B6 1F3FF - \; fully-qualified # 🚶🏿 E2.0 person walkin - g: dark skin tone\n1F6B6 200D 2642 FE0F \; fully-qua - lified # 🚶‍♂️ E4.0 man walking\n1F6B6 200D 2642 - \; minimally-qualified # 🚶‍♂ E4.0 man walking\n1F6B6 - 1F3FB 200D 2642 FE0F \; fully-qualified # 🚶🏻‍ - ♂️ E4.0 man walking: light skin tone\n1F6B6 1F3FB 200D 2642 - \; minimally-qualified # 🚶🏻‍♂ E4.0 man walking: light - skin tone\n1F6B6 1F3FC 200D 2642 FE0F \; fully-qualified - # 🚶🏼‍♂️ E4.0 man walking: medium-light skin tone\n1F6B6 1F - 3FC 200D 2642 \; minimally-qualified # 🚶🏼‍♂ - E4.0 man walking: medium-light skin tone\n1F6B6 1F3FD 200D 2642 FE0F - \; fully-qualified # 🚶🏽‍♂️ E4.0 man walking: m - edium skin tone\n1F6B6 1F3FD 200D 2642 \; minimally-q - ualified # 🚶🏽‍♂ E4.0 man walking: medium skin tone\n1F6B6 1F3FE - 200D 2642 FE0F \; fully-qualified # 🚶🏾‍♂️ - E4.0 man walking: medium-dark skin tone\n1F6B6 1F3FE 200D 2642 - \; minimally-qualified # 🚶🏾‍♂ E4.0 man walking: medium - -dark skin tone\n1F6B6 1F3FF 200D 2642 FE0F \; fully-quali - fied # 🚶🏿‍♂️ E4.0 man walking: dark skin tone\n1F6B6 1F3FF - 200D 2642 \; minimally-qualified # 🚶🏿‍♂ E4 - .0 man walking: dark skin tone\n1F6B6 200D 2640 FE0F - \; fully-qualified # 🚶‍♀️ E4.0 woman walking\n1F6B6 200D 264 - 0 \; minimally-qualified # 🚶‍♀ E4.0 woma - n walking\n1F6B6 1F3FB 200D 2640 FE0F \; fully-qualified - # 🚶🏻‍♀️ E4.0 woman walking: light skin tone\n1F6B6 1F3FB 20 - 0D 2640 \; minimally-qualified # 🚶🏻‍♀ E4.0 - woman walking: light skin tone\n1F6B6 1F3FC 200D 2640 FE0F - \; fully-qualified # 🚶🏼‍♀️ E4.0 woman walking: medium-lig - ht skin tone\n1F6B6 1F3FC 200D 2640 \; minimally-qual - ified # 🚶🏼‍♀ E4.0 woman walking: medium-light skin tone\n1F6B6 1 - F3FD 200D 2640 FE0F \; fully-qualified # 🚶🏽‍ - ♀️ E4.0 woman walking: medium skin tone\n1F6B6 1F3FD 200D 2640 - \; minimally-qualified # 🚶🏽‍♀ E4.0 woman walking: - medium skin tone\n1F6B6 1F3FE 200D 2640 FE0F \; fully-qual - ified # 🚶🏾‍♀️ E4.0 woman walking: medium-dark skin tone\n1 - F6B6 1F3FE 200D 2640 \; minimally-qualified # 🚶 - 🏾‍♀ E4.0 woman walking: medium-dark skin tone\n1F6B6 1F3FF 200D 264 - 0 FE0F \; fully-qualified # 🚶🏿‍♀️ E4.0 wom - an walking: dark skin tone\n1F6B6 1F3FF 200D 2640 \; - minimally-qualified # 🚶🏿‍♀ E4.0 woman walking: dark skin tone\n1 - F9CD \; fully-qualified # 🧍 E1 - 2.1 person standing\n1F9CD 1F3FB \; fully-q - ualified # 🧍🏻 E12.1 person standing: light skin tone\n1F9CD 1F3F - C \; fully-qualified # 🧍🏼 E12.1 p - erson standing: medium-light skin tone\n1F9CD 1F3FD - \; fully-qualified # 🧍🏽 E12.1 person standing: medium s - kin tone\n1F9CD 1F3FE \; fully-qualified - # 🧍🏾 E12.1 person standing: medium-dark skin tone\n1F9CD 1F3FF - \; fully-qualified # 🧍🏿 E12.1 person - standing: dark skin tone\n1F9CD 200D 2642 FE0F \; f - ully-qualified # 🧍‍♂️ E12.1 man standing\n1F9CD 200D 2642 - \; minimally-qualified # 🧍‍♂ E12.1 man stan - ding\n1F9CD 1F3FB 200D 2642 FE0F \; fully-qualified # - 🧍🏻‍♂️ E12.1 man standing: light skin tone\n1F9CD 1F3FB 200D 26 - 42 \; minimally-qualified # 🧍🏻‍♂ E12.1 man - standing: light skin tone\n1F9CD 1F3FC 200D 2642 FE0F \; f - ully-qualified # 🧍🏼‍♂️ E12.1 man standing: medium-light sk - in tone\n1F9CD 1F3FC 200D 2642 \; minimally-qualified - # 🧍🏼‍♂ E12.1 man standing: medium-light skin tone\n1F9CD 1F3FD - 200D 2642 FE0F \; fully-qualified # 🧍🏽‍♂️ - E12.1 man standing: medium skin tone\n1F9CD 1F3FD 200D 2642 - \; minimally-qualified # 🧍🏽‍♂ E12.1 man standing: medium - skin tone\n1F9CD 1F3FE 200D 2642 FE0F \; fully-qualified - # 🧍🏾‍♂️ E12.1 man standing: medium-dark skin tone\n1F9CD 1F - 3FE 200D 2642 \; minimally-qualified # 🧍🏾‍♂ - E12.1 man standing: medium-dark skin tone\n1F9CD 1F3FF 200D 2642 FE0F - \; fully-qualified # 🧍🏿‍♂️ E12.1 man standing - : dark skin tone\n1F9CD 1F3FF 200D 2642 \; minimally- - qualified # 🧍🏿‍♂ E12.1 man standing: dark skin tone\n1F9CD 200D - 2640 FE0F \; fully-qualified # 🧍‍♀️ E12 - .1 woman standing\n1F9CD 200D 2640 \; minimally - -qualified # 🧍‍♀ E12.1 woman standing\n1F9CD 1F3FB 200D 2640 FE0F - \; fully-qualified # 🧍🏻‍♀️ E12.1 woman stan - ding: light skin tone\n1F9CD 1F3FB 200D 2640 \; minim - ally-qualified # 🧍🏻‍♀ E12.1 woman standing: light skin tone\n1F9 - CD 1F3FC 200D 2640 FE0F \; fully-qualified # 🧍🏼 - ‍♀️ E12.1 woman standing: medium-light skin tone\n1F9CD 1F3FC 200D 2 - 640 \; minimally-qualified # 🧍🏼‍♀ E12.1 wom - an standing: medium-light skin tone\n1F9CD 1F3FD 200D 2640 FE0F - \; fully-qualified # 🧍🏽‍♀️ E12.1 woman standing: med - ium skin tone\n1F9CD 1F3FD 200D 2640 \; minimally-qua - lified # 🧍🏽‍♀ E12.1 woman standing: medium skin tone\n1F9CD 1F3F - E 200D 2640 FE0F \; fully-qualified # 🧍🏾‍♀ - ️ E12.1 woman standing: medium-dark skin tone\n1F9CD 1F3FE 200D 2640 - \; minimally-qualified # 🧍🏾‍♀ E12.1 woman stan - ding: medium-dark skin tone\n1F9CD 1F3FF 200D 2640 FE0F \; - fully-qualified # 🧍🏿‍♀️ E12.1 woman standing: dark skin t - one\n1F9CD 1F3FF 200D 2640 \; minimally-qualified # - 🧍🏿‍♀ E12.1 woman standing: dark skin tone\n1F9CE - \; fully-qualified # 🧎 E12.1 person kneeling\ - n1F9CE 1F3FB \; fully-qualified # 🧎 - 🏻 E12.1 person kneeling: light skin tone\n1F9CE 1F3FC - \; fully-qualified # 🧎🏼 E12.1 person kneeling: med - ium-light skin tone\n1F9CE 1F3FD \; fully-q - ualified # 🧎🏽 E12.1 person kneeling: medium skin tone\n1F9CE 1F3 - FE \; fully-qualified # 🧎🏾 E12.1 - person kneeling: medium-dark skin tone\n1F9CE 1F3FF - \; fully-qualified # 🧎🏿 E12.1 person kneeling: dark ski - n tone\n1F9CE 200D 2642 FE0F \; fully-qualified - # 🧎‍♂️ E12.1 man kneeling\n1F9CE 200D 2642 - \; minimally-qualified # 🧎‍♂ E12.1 man kneeling\n1F9CE 1F3FB 2 - 00D 2642 FE0F \; fully-qualified # 🧎🏻‍♂️ E - 12.1 man kneeling: light skin tone\n1F9CE 1F3FB 200D 2642 - \; minimally-qualified # 🧎🏻‍♂ E12.1 man kneeling: light ski - n tone\n1F9CE 1F3FC 200D 2642 FE0F \; fully-qualified - # 🧎🏼‍♂️ E12.1 man kneeling: medium-light skin tone\n1F9CE 1F3F - C 200D 2642 \; minimally-qualified # ��🏼‍♂ - E12.1 man kneeling: medium-light skin tone\n1F9CE 1F3FD 200D 2642 FE0F - \; fully-qualified # 🧎🏽‍♂️ E12.1 man kneelin - g: medium skin tone\n1F9CE 1F3FD 200D 2642 \; minimal - ly-qualified # 🧎🏽‍♂ E12.1 man kneeling: medium skin tone\n1F9CE - 1F3FE 200D 2642 FE0F \; fully-qualified # 🧎🏾‍ - ♂️ E12.1 man kneeling: medium-dark skin tone\n1F9CE 1F3FE 200D 2642 - \; minimally-qualified # 🧎🏾‍♂ E12.1 man kneel - ing: medium-dark skin tone\n1F9CE 1F3FF 200D 2642 FE0F \; - fully-qualified # 🧎🏿‍♂️ E12.1 man kneeling: dark skin tone - \n1F9CE 1F3FF 200D 2642 \; minimally-qualified # 🧎 - 🏿‍♂ E12.1 man kneeling: dark skin tone\n1F9CE 200D 2640 FE0F - \; fully-qualified # 🧎‍♀️ E12.1 woman kneelin - g\n1F9CE 200D 2640 \; minimally-qualified # - 🧎‍♀ E12.1 woman kneeling\n1F9CE 1F3FB 200D 2640 FE0F - \; fully-qualified # 🧎🏻‍♀️ E12.1 woman kneeling: light s - kin tone\n1F9CE 1F3FB 200D 2640 \; minimally-qualifie - d # 🧎🏻‍♀ E12.1 woman kneeling: light skin tone\n1F9CE 1F3FC 200D - 2640 FE0F \; fully-qualified # 🧎🏼‍♀️ E12. - 1 woman kneeling: medium-light skin tone\n1F9CE 1F3FC 200D 2640 - \; minimally-qualified # 🧎��‍♀ E12.1 woman kneeling: - medium-light skin tone\n1F9CE 1F3FD 200D 2640 FE0F \; ful - ly-qualified # 🧎🏽‍♀️ E12.1 woman kneeling: medium skin ton - e\n1F9CE 1F3FD 200D 2640 \; minimally-qualified # - 🧎🏽‍♀ E12.1 woman kneeling: medium skin tone\n1F9CE 1F3FE 200D 26 - 40 FE0F \; fully-qualified # 🧎🏾‍♀️ E12.1 w - oman kneeling: medium-dark skin tone\n1F9CE 1F3FE 200D 2640 - \; minimally-qualified # 🧎🏾‍♀ E12.1 woman kneeling: mediu - m-dark skin tone\n1F9CE 1F3FF 200D 2640 FE0F \; fully-qual - ified # 🧎🏿‍♀️ E12.1 woman kneeling: dark skin tone\n1F9CE - 1F3FF 200D 2640 \; minimally-qualified # 🧎🏿‍ - ♀ E12.1 woman kneeling: dark skin tone\n1F9D1 200D 1F9AF - \; fully-qualified # 🧑‍🦯 E12.1 person with probing - cane\n1F9D1 1F3FB 200D 1F9AF \; fully-qualified # - 🧑🏻‍🦯 E12.1 person with probing cane: light skin tone\n1F9D1 1F3 - FC 200D 1F9AF \; fully-qualified # 🧑🏼‍🦯 - E12.1 person with probing cane: medium-light skin tone\n1F9D1 1F3FD 200D - 1F9AF \; fully-qualified # 🧑🏽‍🦯 E12.1 p - erson with probing cane: medium skin tone\n1F9D1 1F3FE 200D 1F9AF - \; fully-qualified # 🧑🏾‍🦯 E12.1 person with pro - bing cane: medium-dark skin tone\n1F9D1 1F3FF 200D 1F9AF - \; fully-qualified # 🧑🏿‍🦯 E12.1 person with probing cane - : dark skin tone\n1F468 200D 1F9AF \; fully-qual - ified # 👨‍🦯 E12.1 man with probing cane\n1F468 1F3FB 200D 1F9A - F \; fully-qualified # 👨🏻‍🦯 E12.1 man w - ith probing cane: light skin tone\n1F468 1F3FC 200D 1F9AF - \; fully-qualified # 👨🏼‍🦯 E12.1 man with probing cane: - medium-light skin tone\n1F468 1F3FD 200D 1F9AF \; full - y-qualified # 👨🏽‍🦯 E12.1 man with probing cane: medium skin - tone\n1F468 1F3FE 200D 1F9AF \; fully-qualified # - 👨🏾‍🦯 E12.1 man with probing cane: medium-dark skin tone\n1F468 - 1F3FF 200D 1F9AF \; fully-qualified # 👨🏿‍ - 🦯 E12.1 man with probing cane: dark skin tone\n1F469 200D 1F9AF - \; fully-qualified # 👩‍🦯 E12.1 woman with p - robing cane\n1F469 1F3FB 200D 1F9AF \; fully-qualified - # 👩🏻‍🦯 E12.1 woman with probing cane: light skin tone\n1F4 - 69 1F3FC 200D 1F9AF \; fully-qualified # 👩🏼 - ‍🦯 E12.1 woman with probing cane: medium-light skin tone\n1F469 1F3FD - 200D 1F9AF \; fully-qualified # 👩🏽‍🦯 E - 12.1 woman with probing cane: medium skin tone\n1F469 1F3FE 200D 1F9AF - \; fully-qualified # 👩��‍🦯 E12.1 woman wi - th probing cane: medium-dark skin tone\n1F469 1F3FF 200D 1F9AF - \; fully-qualified # 👩🏿‍🦯 E12.1 woman with probing - cane: dark skin tone\n1F9D1 200D 1F9BC \; fully - -qualified # 🧑‍🦼 E12.1 person in motorized wheelchair\n1F9D1 1 - F3FB 200D 1F9BC \; fully-qualified # 🧑🏻‍ - 🦼 E12.1 person in motorized wheelchair: light skin tone\n1F9D1 1F3FC 20 - 0D 1F9BC \; fully-qualified # 🧑🏼‍🦼 E12. - 1 person in motorized wheelchair: medium-light skin tone\n1F9D1 1F3FD 200D - 1F9BC \; fully-qualified # 🧑🏽‍🦼 E12.1 - person in motorized wheelchair: medium skin tone\n1F9D1 1F3FE 200D 1F9BC - \; fully-qualified # 🧑🏾‍🦼 E12.1 person i - n motorized wheelchair: medium-dark skin tone\n1F9D1 1F3FF 200D 1F9BC - \; fully-qualified # 🧑🏿‍🦼 E12.1 person in m - otorized wheelchair: dark skin tone\n1F468 200D 1F9BC - \; fully-qualified # 👨‍🦼 E12.1 man in motorized wheelcha - ir\n1F468 1F3FB 200D 1F9BC \; fully-qualified # - 👨🏻‍🦼 E12.1 man in motorized wheelchair: light skin tone\n1F468 - 1F3FC 200D 1F9BC \; fully-qualified # 👨🏼‍ - 🦼 E12.1 man in motorized wheelchair: medium-light skin tone\n1F468 1F3F - D 200D 1F9BC \; fully-qualified # 👨🏽‍🦼 - E12.1 man in motorized wheelchair: medium skin tone\n1F468 1F3FE 200D 1F9B - C \; fully-qualified # 👨🏾‍🦼 E12.1 man i - n motorized wheelchair: medium-dark skin tone\n1F468 1F3FF 200D 1F9BC - \; fully-qualified # 👨🏿‍🦼 E12.1 man in moto - rized wheelchair: dark skin tone\n1F469 200D 1F9BC - \; fully-qualified # 👩‍🦼 E12.1 woman in motorized wheelchai - r\n1F469 1F3FB 200D 1F9BC \; fully-qualified # - 👩🏻‍🦼 E12.1 woman in motorized wheelchair: light skin tone\n1F46 - 9 1F3FC 200D 1F9BC \; fully-qualified # 👩🏼 - ‍🦼 E12.1 woman in motorized wheelchair: medium-light skin tone\n1F469 - 1F3FD 200D 1F9BC \; fully-qualified # 👩🏽‍ - 🦼 E12.1 woman in motorized wheelchair: medium skin tone\n1F469 1F3FE 20 - 0D 1F9BC \; fully-qualified # 👩🏾‍🦼 E12. - 1 woman in motorized wheelchair: medium-dark skin tone\n1F469 1F3FF 200D 1 - F9BC \; fully-qualified # 👩🏿‍🦼 E12.1 wo - man in motorized wheelchair: dark skin tone\n1F9D1 200D 1F9BD - \; fully-qualified # 🧑‍🦽 E12.1 person in manual - wheelchair\n1F9D1 1F3FB 200D 1F9BD \; fully-qualified - # 🧑🏻‍🦽 E12.1 person in manual wheelchair: light skin tone\n - 1F9D1 1F3FC 200D 1F9BD \; fully-qualified # 🧑 - 🏼‍🦽 E12.1 person in manual wheelchair: medium-light skin tone\n1F9 - D1 1F3FD 200D 1F9BD \; fully-qualified # 🧑🏽 - ‍🦽 E12.1 person in manual wheelchair: medium skin tone\n1F9D1 1F3FE 2 - 00D 1F9BD \; fully-qualified # 🧑🏾‍🦽 E12 - .1 person in manual wheelchair: medium-dark skin tone\n1F9D1 1F3FF 200D 1F - 9BD \; fully-qualified # 🧑🏿‍🦽 E12.1 per - son in manual wheelchair: dark skin tone\n1F468 200D 1F9BD - \; fully-qualified # 👨‍🦽 E12.1 man in manual wheelc - hair\n1F468 1F3FB 200D 1F9BD \; fully-qualified # - 👨🏻‍🦽 E12.1 man in manual wheelchair: light skin tone\n1F468 1F3 - FC 200D 1F9BD \; fully-qualified # 👨🏼‍🦽 - E12.1 man in manual wheelchair: medium-light skin tone\n1F468 1F3FD 200D - 1F9BD \; fully-qualified # 👨🏽‍🦽 E12.1 m - an in manual wheelchair: medium skin tone\n1F468 1F3FE 200D 1F9BD - \; fully-qualified # 👨🏾‍🦽 E12.1 man in manual w - heelchair: medium-dark skin tone\n1F468 1F3FF 200D 1F9BD - \; fully-qualified # 👨🏿‍🦽 E12.1 man in manual wheelchair - : dark skin tone\n1F469 200D 1F9BD \; fully-qual - ified # 👩‍🦽 E12.1 woman in manual wheelchair\n1F469 1F3FB 200D - 1F9BD \; fully-qualified # 👩🏻‍🦽 E12.1 - woman in manual wheelchair: light skin tone\n1F469 1F3FC 200D 1F9BD - \; fully-qualified # 👩🏼‍🦽 E12.1 woman in manu - al wheelchair: medium-light skin tone\n1F469 1F3FD 200D 1F9BD - \; fully-qualified # 👩🏽‍🦽 E12.1 woman in manual whe - elchair: medium skin tone\n1F469 1F3FE 200D 1F9BD \; f - ully-qualified # 👩🏾‍�� E12.1 woman in manual wheelchair: m - edium-dark skin tone\n1F469 1F3FF 200D 1F9BD \; fully- - qualified # 👩🏿‍🦽 E12.1 woman in manual wheelchair: dark ski - n tone\n1F3C3 \; fully-qualified - # 🏃 E2.0 person running\n1F3C3 1F3FB \; - fully-qualified # 🏃🏻 E2.0 person running: light skin tone\n1F3C3 - 1F3FC \; fully-qualified # 🏃🏼 E2 - .0 person running: medium-light skin tone\n1F3C3 1F3FD - \; fully-qualified # 🏃🏽 E2.0 person running: medium - skin tone\n1F3C3 1F3FE \; fully-qualified - # 🏃🏾 E2.0 person running: medium-dark skin tone\n1F3C3 1F3FF - \; fully-qualified # 🏃🏿 E2.0 person r - unning: dark skin tone\n1F3C3 200D 2642 FE0F \; full - y-qualified # 🏃‍♂️ E4.0 man running\n1F3C3 200D 2642 - \; minimally-qualified # 🏃‍♂ E4.0 man running\n1 - F3C3 1F3FB 200D 2642 FE0F \; fully-qualified # 🏃 - 🏻‍♂️ E4.0 man running: light skin tone\n1F3C3 1F3FB 200D 2642 - \; minimally-qualified # 🏃🏻‍♂ E4.0 man running - : light skin tone\n1F3C3 1F3FC 200D 2642 FE0F \; fully-qua - lified # 🏃🏼‍♂️ E4.0 man running: medium-light skin tone\n1 - F3C3 1F3FC 200D 2642 \; minimally-qualified # 🏃 - 🏼‍♂ E4.0 man running: medium-light skin tone\n1F3C3 1F3FD 200D 2642 - FE0F \; fully-qualified # 🏃🏽‍♂️ E4.0 man - running: medium skin tone\n1F3C3 1F3FD 200D 2642 \; m - inimally-qualified # 🏃🏽‍♂ E4.0 man running: medium skin tone\n1F - 3C3 1F3FE 200D 2642 FE0F \; fully-qualified # 🏃🏾 - ‍♂️ E4.0 man running: medium-dark skin tone\n1F3C3 1F3FE 200D 2642 - \; minimally-qualified # 🏃🏾‍♂ E4.0 man runni - ng: medium-dark skin tone\n1F3C3 1F3FF 200D 2642 FE0F \; f - ully-qualified # 🏃🏿‍♂️ E4.0 man running: dark skin tone\n1 - F3C3 1F3FF 200D 2642 \; minimally-qualified # 🏃 - 🏿‍♂ E4.0 man running: dark skin tone\n1F3C3 200D 2640 FE0F - \; fully-qualified # 🏃‍♀️ E4.0 woman running\n1 - F3C3 200D 2640 \; minimally-qualified # 🏃‍ - ♀ E4.0 woman running\n1F3C3 1F3FB 200D 2640 FE0F \; full - y-qualified # 🏃🏻‍♀️ E4.0 woman running: light skin tone\n1 - F3C3 1F3FB 200D 2640 \; minimally-qualified # 🏃 - 🏻‍♀ E4.0 woman running: light skin tone\n1F3C3 1F3FC 200D 2640 FE0F - \; fully-qualified # 🏃🏼‍♀️ E4.0 woman run - ning: medium-light skin tone\n1F3C3 1F3FC 200D 2640 \ - ; minimally-qualified # 🏃🏼‍♀ E4.0 woman running: medium-light sk - in tone\n1F3C3 1F3FD 200D 2640 FE0F \; fully-qualified - # 🏃🏽‍♀️ E4.0 woman running: medium skin tone\n1F3C3 1F3FD 200 - D 2640 \; minimally-qualified # 🏃🏽‍♀ E4.0 w - oman running: medium skin tone\n1F3C3 1F3FE 200D 2640 FE0F - \; fully-qualified # 🏃🏾‍♀️ E4.0 woman running: medium-dar - k skin tone\n1F3C3 1F3FE 200D 2640 \; minimally-quali - fied # ��🏾‍♀ E4.0 woman running: medium-dark skin tone\n1F3C3 1 - F3FF 200D 2640 FE0F \; fully-qualified # 🏃🏿‍ - ♀️ E4.0 woman running: dark skin tone\n1F3C3 1F3FF 200D 2640 - \; minimally-qualified # 🏃🏿‍♀ E4.0 woman running: da - rk skin tone\n1F483 \; fully-qualifie - d # 💃 E2.0 woman dancing\n1F483 1F3FB - \; fully-qualified # 💃🏻 E2.0 woman dancing: light skin tone\n1 - F483 1F3FC \; fully-qualified # 💃 - 🏼 E2.0 woman dancing: medium-light skin tone\n1F483 1F3FD - \; fully-qualified # 💃🏽 E2.0 woman dancing: me - dium skin tone\n1F483 1F3FE \; fully-qualif - ied # 💃🏾 E2.0 woman dancing: medium-dark skin tone\n1F483 1F3FF - \; fully-qualified # 💃🏿 E2.0 woma - n dancing: dark skin tone\n1F57A \; f - ully-qualified # 🕺 E4.0 man dancing\n1F57A 1F3FB - \; fully-qualified # 🕺🏻 E4.0 man dancing: light ski - n tone\n1F57A 1F3FC \; fully-qualified - # 🕺🏼 E4.0 man dancing: medium-light skin tone\n1F57A 1F3FD - \; fully-qualified # 🕺🏽 E4.0 man dancing: - medium skin tone\n1F57A 1F3FE \; fully-qual - ified # 🕺🏾 E4.0 man dancing: medium-dark skin tone\n1F57A 1F3FF - \; fully-qualified # 🕺🏿 E4.0 man - dancing: dark skin tone\n1F574 FE0F \; ful - ly-qualified # 🕴️ E2.0 man in suit levitating\n1F574 - \; unqualified # 🕴 E2.0 man in suit le - vitating\n1F574 1F3FB \; fully-qualified - # 🕴🏻 E4.0 man in suit levitating: light skin tone\n1F574 1F3FC - \; fully-qualified # 🕴🏼 E4.0 man in - suit levitating: medium-light skin tone\n1F574 1F3FD - \; fully-qualified # 🕴🏽 E4.0 man in suit levitating: m - edium skin tone\n1F574 1F3FE \; fully-quali - fied # 🕴🏾 E4.0 man in suit levitating: medium-dark skin tone\n1F - 574 1F3FF \; fully-qualified # 🕴🏿 - E4.0 man in suit levitating: dark skin tone\n1F46F - \; fully-qualified # 👯 E2.0 people with bunny ears\n - 1F46F 200D 2642 FE0F \; fully-qualified # 👯 - ‍♂️ E4.0 men with bunny ears\n1F46F 200D 2642 - \; minimally-qualified # 👯‍♂ E4.0 men with bunny ears\n1F46F 2 - 00D 2640 FE0F \; fully-qualified # 👯‍♀️ - E4.0 women with bunny ears\n1F46F 200D 2640 \; - minimally-qualified # 👯‍♀ E4.0 women with bunny ears\n1F9D6 - \; fully-qualified # 🧖 E5.0 person i - n steamy room\n1F9D6 1F3FB \; fully-qualifi - ed # 🧖🏻 E5.0 person in steamy room: light skin tone\n1F9D6 1F3FC - \; fully-qualified # 🧖🏼 E5.0 per - son in steamy room: medium-light skin tone\n1F9D6 1F3FD - \; fully-qualified # 🧖🏽 E5.0 person in steamy room: - medium skin tone\n1F9D6 1F3FE \; fully-qua - lified # 🧖🏾 E5.0 person in steamy room: medium-dark skin tone\n1 - F9D6 1F3FF \; fully-qualified # 🧖 - 🏿 E5.0 person in steamy room: dark skin tone\n1F9D6 200D 2642 FE0F - \; fully-qualified # 🧖‍♂️ E5.0 man in steam - y room\n1F9D6 200D 2642 \; minimally-qualified - # 🧖‍♂ E5.0 man in steamy room\n1F9D6 1F3FB 200D 2642 FE0F - \; fully-qualified # 🧖🏻‍♂️ E5.0 man in steamy room: - light skin tone\n1F9D6 1F3FB 200D 2642 \; minimally- - qualified # 🧖🏻‍♂ E5.0 man in steamy room: light skin tone\n1F9D6 - 1F3FC 200D 2642 FE0F \; fully-qualified # 🧖🏼‍ - ♂️ E5.0 man in steamy room: medium-light skin tone\n1F9D6 1F3FC 200D 2 - 642 \; minimally-qualified # 🧖🏼‍♂ E5.0 man - in steamy room: medium-light skin tone\n1F9D6 1F3FD 200D 2642 FE0F - \; fully-qualified # 🧖🏽‍♂️ E5.0 man in steamy roo - m: medium skin tone\n1F9D6 1F3FD 200D 2642 \; minimal - ly-qualified # 🧖🏽‍♂ E5.0 man in steamy room: medium skin tone\n1 - F9D6 1F3FE 200D 2642 FE0F \; fully-qualified # 🧖 - 🏾‍♂️ E5.0 man in steamy room: medium-dark skin tone\n1F9D6 1F3FE - 200D 2642 \; minimally-qualified # 🧖🏾‍♂ E5. - 0 man in steamy room: medium-dark skin tone\n1F9D6 1F3FF 200D 2642 FE0F - \; fully-qualified # 🧖🏿‍♂️ E5.0 man in steam - y room: dark skin tone\n1F9D6 1F3FF 200D 2642 \; mini - mally-qualified # 🧖🏿‍♂ E5.0 man in steamy room: dark skin tone\n - 1F9D6 200D 2640 FE0F \; fully-qualified # 🧖 - ‍♀️ E5.0 woman in steamy room\n1F9D6 200D 2640 - \; minimally-qualified # 🧖‍♀ E5.0 woman in steamy room\n1F9D6 - 1F3FB 200D 2640 FE0F \; fully-qualified # 🧖🏻‍ - ♀️ E5.0 woman in steamy room: light skin tone\n1F9D6 1F3FB 200D 2640 - \; minimally-qualified # 🧖🏻‍♀ E5.0 woman in - steamy room: light skin tone\n1F9D6 1F3FC 200D 2640 FE0F \ - ; fully-qualified # 🧖🏼‍♀️ E5.0 woman in steamy room: mediu - m-light skin tone\n1F9D6 1F3FC 200D 2640 \; minimally - -qualified # 🧖🏼‍♀ E5.0 woman in steamy room: medium-light skin t - one\n1F9D6 1F3FD 200D 2640 FE0F \; fully-qualified # - 🧖🏽‍♀️ E5.0 woman in steamy room: medium skin tone\n1F9D6 1F3FD - 200D 2640 \; minimally-qualified # 🧖🏽‍♀ E5 - .0 woman in steamy room: medium skin tone\n1F9D6 1F3FE 200D 2640 FE0F - \; fully-qualified # 🧖🏾‍♀️ E5.0 woman in steam - y room: medium-dark skin tone\n1F9D6 1F3FE 200D 2640 - \; minimally-qualified # 🧖🏾‍♀ E5.0 woman in steamy room: medium- - dark skin tone\n1F9D6 1F3FF 200D 2640 FE0F \; fully-qualif - ied # 🧖🏿‍♀️ E5.0 woman in steamy room: dark skin tone\n1F9 - D6 1F3FF 200D 2640 \; minimally-qualified # 🧖🏿 - ‍♀ E5.0 woman in steamy room: dark skin tone\n1F9D7 - \; fully-qualified # 🧗 E5.0 person climbing\n1F9 - D7 1F3FB \; fully-qualified # 🧗🏻 - E5.0 person climbing: light skin tone\n1F9D7 1F3FC - \; fully-qualified # ��🏼 E5.0 person climbing: medium-l - ight skin tone\n1F9D7 1F3FD \; fully-qualif - ied # 🧗🏽 E5.0 person climbing: medium skin tone\n1F9D7 1F3FE - \; fully-qualified # 🧗🏾 E5.0 person - climbing: medium-dark skin tone\n1F9D7 1F3FF - \; fully-qualified # 🧗🏿 E5.0 person climbing: dark skin tone\n - 1F9D7 200D 2642 FE0F \; fully-qualified # 🧗 - ‍♂️ E5.0 man climbing\n1F9D7 200D 2642 \; - minimally-qualified # 🧗‍♂ E5.0 man climbing\n1F9D7 1F3FB 200D 2642 - FE0F \; fully-qualified # 🧗🏻‍♂️ E5.0 man - climbing: light skin tone\n1F9D7 1F3FB 200D 2642 \; m - inimally-qualified # 🧗🏻‍♂ E5.0 man climbing: light skin tone\n1F - 9D7 1F3FC 200D 2642 FE0F \; fully-qualified # 🧗🏼 - ‍♂️ E5.0 man climbing: medium-light skin tone\n1F9D7 1F3FC 200D 2642 - \; minimally-qualified # 🧗🏼‍♂ E5.0 man cli - mbing: medium-light skin tone\n1F9D7 1F3FD 200D 2642 FE0F - \; fully-qualified # 🧗🏽‍♂️ E5.0 man climbing: medium skin - tone\n1F9D7 1F3FD 200D 2642 \; minimally-qualified # - 🧗🏽‍♂ E5.0 man climbing: medium skin tone\n1F9D7 1F3FE 200D 2642 - FE0F \; fully-qualified # 🧗🏾‍♂️ E5.0 man c - limbing: medium-dark skin tone\n1F9D7 1F3FE 200D 2642 - \; minimally-qualified # 🧗🏾‍♂ E5.0 man climbing: medium-dark sk - in tone\n1F9D7 1F3FF 200D 2642 FE0F \; fully-qualified - # 🧗🏿‍♂️ E5.0 man climbing: dark skin tone\n1F9D7 1F3FF 200D 2 - 642 \; minimally-qualified # 🧗🏿‍♂ E5.0 man - climbing: dark skin tone\n1F9D7 200D 2640 FE0F \; fu - lly-qualified # 🧗‍♀️ E5.0 woman climbing\n1F9D7 200D 2640 - \; minimally-qualified # 🧗‍♀ E5.0 woman cli - mbing\n1F9D7 1F3FB 200D 2640 FE0F \; fully-qualified # - 🧗🏻‍♀️ E5.0 woman climbing: light skin tone\n1F9D7 1F3FB 200D - 2640 \; minimally-qualified # 🧗🏻‍♀ E5.0 wom - an climbing: light skin tone\n1F9D7 1F3FC 200D 2640 FE0F \ - ; fully-qualified # 🧗🏼‍♀️ E5.0 woman climbing: medium-ligh - t skin tone\n1F9D7 1F3FC 200D 2640 \; minimally-quali - fied # 🧗🏼‍♀ E5.0 woman climbing: medium-light skin tone\n1F9D7 1 - F3FD 200D 2640 FE0F \; fully-qualified # 🧗🏽‍ - ♀️ E5.0 woman climbing: medium skin tone\n1F9D7 1F3FD 200D 2640 - \; minimally-qualified # 🧗🏽‍♀ E5.0 woman climbing - : medium skin tone\n1F9D7 1F3FE 200D 2640 FE0F \; fully-qu - alified # 🧗🏾‍♀️ E5.0 woman climbing: medium-dark skin tone - \n1F9D7 1F3FE 200D 2640 \; minimally-qualified # 🧗 - 🏾‍♀ E5.0 woman climbing: medium-dark skin tone\n1F9D7 1F3FF 200D 26 - 40 FE0F \; fully-qualified # 🧗🏿‍♀️ E5.0 wo - man climbing: dark skin tone\n1F9D7 1F3FF 200D 2640 \ - ; minimally-qualified # 🧗🏿‍♀ E5.0 woman climbing: dark skin tone - \n\n# subgroup: person-sport\n1F93A \ - ; fully-qualified # 🤺 E4.0 person fencing\n1F3C7 - \; fully-qualified # 🏇 E2.0 horse racing\n1F3C7 - 1F3FB \; fully-qualified # 🏇🏻 E4. - 0 horse racing: light skin tone\n1F3C7 1F3FC - \; fully-qualified # 🏇🏼 E4.0 horse racing: medium-light skin t - one\n1F3C7 1F3FD \; fully-qualified # - 🏇🏽 E4.0 horse racing: medium skin tone\n1F3C7 1F3FE - \; fully-qualified # 🏇🏾 E4.0 horse racing: medium - -dark skin tone\n1F3C7 1F3FF \; fully-quali - fied # 🏇🏿 E4.0 horse racing: dark skin tone\n26F7 FE0F - \; fully-qualified # ⛷️ E2.0 skier\n26F7 - \; unqualified # ⛷ E2.0 ski - er\n1F3C2 \; fully-qualified # - 🏂 E2.0 snowboarder\n1F3C2 1F3FB \; fully - -qualified # 🏂🏻 E4.0 snowboarder: light skin tone\n1F3C2 1F3FC - \; fully-qualified # 🏂🏼 E4.0 snowb - oarder: medium-light skin tone\n1F3C2 1F3FD - \; fully-qualified # 🏂🏽 E4.0 snowboarder: medium skin tone\n1F3 - C2 1F3FE \; fully-qualified # 🏂🏾 - E4.0 snowboarder: medium-dark skin tone\n1F3C2 1F3FF - \; fully-qualified # 🏂🏿 E4.0 snowboarder: dark skin to - ne\n1F3CC FE0F \; fully-qualified # - 🏌️ E2.0 person golfing\n1F3CC \; - unqualified # 🏌 E2.0 person golfing\n1F3CC 1F3FB - \; fully-qualified # 🏌🏻 E4.0 person golfing: l - ight skin tone\n1F3CC 1F3FC \; fully-qualif - ied # 🏌🏼 E4.0 person golfing: medium-light skin tone\n1F3CC 1F3F - D \; fully-qualified # 🏌🏽 E4.0 pe - rson golfing: medium skin tone\n1F3CC 1F3FE - \; fully-qualified # 🏌🏾 E4.0 person golfing: medium-dark skin t - one\n1F3CC 1F3FF \; fully-qualified # - 🏌🏿 E4.0 person golfing: dark skin tone\n1F3CC FE0F 200D 2642 FE0F - \; fully-qualified # 🏌️‍♂️ E4.0 man golfing\ - n1F3CC 200D 2642 FE0F \; unqualified # 🏌 - ‍♂️ E4.0 man golfing\n1F3CC FE0F 200D 2642 \; - unqualified # 🏌️‍♂ E4.0 man golfing\n1F3CC 200D 2642 - \; unqualified # 🏌‍♂ E4.0 man golfin - g\n1F3CC 1F3FB 200D 2642 FE0F \; fully-qualified # - 🏌🏻‍♂️ E4.0 man golfing: light skin tone\n1F3CC 1F3FB 200D 2642 - \; minimally-qualified # 🏌🏻‍♂ E4.0 man gol - fing: light skin tone\n1F3CC 1F3FC 200D 2642 FE0F \; fully - -qualified # 🏌🏼‍♂️ E4.0 man golfing: medium-light skin ton - e\n1F3CC 1F3FC 200D 2642 \; minimally-qualified # - 🏌🏼‍♂ E4.0 man golfing: medium-light skin tone\n1F3CC 1F3FD 200D - 2642 FE0F \; fully-qualified # 🏌🏽‍♂️ E4.0 - man golfing: medium skin tone\n1F3CC 1F3FD 200D 2642 - \; minimally-qualified # 🏌🏽‍♂ E4.0 man golfing: medium skin tone - \n1F3CC 1F3FE 200D 2642 FE0F \; fully-qualified # 🏌 - 🏾‍♂️ E4.0 man golfing: medium-dark skin tone\n1F3CC 1F3FE 200D 26 - 42 \; minimally-qualified # 🏌🏾‍♂ E4.0 man g - olfing: medium-dark skin tone\n1F3CC 1F3FF 200D 2642 FE0F - \; fully-qualified # 🏌🏿‍♂️ E4.0 man golfing: dark skin ton - e\n1F3CC 1F3FF 200D 2642 \; minimally-qualified # - 🏌🏿‍♂ E4.0 man golfing: dark skin tone\n1F3CC FE0F 200D 2640 FE0F - \; fully-qualified # 🏌️‍♀️ E4.0 woman gol - fing\n1F3CC 200D 2640 FE0F \; unqualified # - 🏌‍♀️ E4.0 woman golfing\n1F3CC FE0F 200D 2640 - \; unqualified # 🏌️‍♀ E4.0 woman golfing\n1F3CC 200D 2 - 640 \; unqualified # 🏌‍♀ E4.0 wo - man golfing\n1F3CC 1F3FB 200D 2640 FE0F \; fully-qualified - # 🏌🏻‍♀️ E4.0 woman golfing: light skin tone\n1F3CC 1F3FB - 200D 2640 \; minimally-qualified # 🏌🏻‍♀ E4. - 0 woman golfing: light skin tone\n1F3CC 1F3FC 200D 2640 FE0F - \; fully-qualified # 🏌🏼‍♀️ E4.0 woman golfing: medium-l - ight skin tone\n1F3CC 1F3FC 200D 2640 \; minimally-qu - alified # 🏌🏼‍♀ E4.0 woman golfing: medium-light skin tone\n1F3CC - 1F3FD 200D 2640 FE0F \; fully-qualified # 🏌🏽‍ - ♀️ E4.0 woman golfing: medium skin tone\n1F3CC 1F3FD 200D 2640 - \; minimally-qualified # 🏌🏽‍♀ E4.0 woman golfing: - medium skin tone\n1F3CC 1F3FE 200D 2640 FE0F \; fully-qual - ified # 🏌🏾‍♀️ E4.0 woman golfing: medium-dark skin tone\n1 - F3CC 1F3FE 200D 2640 \; minimally-qualified # 🏌 - 🏾‍♀ E4.0 woman golfing: medium-dark skin tone\n1F3CC 1F3FF 200D 264 - 0 FE0F \; fully-qualified # 🏌🏿‍♀️ E4.0 wom - an golfing: dark skin tone\n1F3CC 1F3FF 200D 2640 \; - minimally-qualified # 🏌🏿‍♀ E4.0 woman golfing: dark skin tone\n1 - F3C4 \; fully-qualified # 🏄 E2 - .0 person surfing\n1F3C4 1F3FB \; fully-qua - lified # 🏄🏻 E2.0 person surfing: light skin tone\n1F3C4 1F3FC - \; fully-qualified # 🏄🏼 E2.0 person - surfing: medium-light skin tone\n1F3C4 1F3FD - \; fully-qualified # 🏄🏽 E2.0 person surfing: medium skin tone - \n1F3C4 1F3FE \; fully-qualified # 🏄 - 🏾 E2.0 person surfing: medium-dark skin tone\n1F3C4 1F3FF - \; fully-qualified # 🏄🏿 E2.0 person surfing: d - ark skin tone\n1F3C4 200D 2642 FE0F \; fully-qualifi - ed # 🏄‍♂️ E4.0 man surfing\n1F3C4 200D 2642 - \; minimally-qualified # 🏄‍♂ E4.0 man surfing\n1F3C4 1F3F - B 200D 2642 FE0F \; fully-qualified # 🏄🏻‍♂ - ️ E4.0 man surfing: light skin tone\n1F3C4 1F3FB 200D 2642 - \; minimally-qualified # 🏄🏻‍♂ E4.0 man surfing: light sk - in tone\n1F3C4 1F3FC 200D 2642 FE0F \; fully-qualified - # 🏄🏼‍♂️ E4.0 man surfing: medium-light skin tone\n1F3C4 1F3FC - 200D 2642 \; minimally-qualified # 🏄��‍♂ - E4.0 man surfing: medium-light skin tone\n1F3C4 1F3FD 200D 2642 FE0F - \; fully-qualified # 🏄🏽‍♂️ E4.0 man surfing: me - dium skin tone\n1F3C4 1F3FD 200D 2642 \; minimally-qu - alified # 🏄🏽‍♂ E4.0 man surfing: medium skin tone\n1F3C4 1F3FE 2 - 00D 2642 FE0F \; fully-qualified # 🏄🏾‍♂️ E - 4.0 man surfing: medium-dark skin tone\n1F3C4 1F3FE 200D 2642 - \; minimally-qualified # 🏄🏾‍♂ E4.0 man surfing: medium- - dark skin tone\n1F3C4 1F3FF 200D 2642 FE0F \; fully-qualif - ied # 🏄🏿‍♂️ E4.0 man surfing: dark skin tone\n1F3C4 1F3FF - 200D 2642 \; minimally-qualified # 🏄🏿‍♂ E4. - 0 man surfing: dark skin tone\n1F3C4 200D 2640 FE0F - \; fully-qualified # 🏄‍♀️ E4.0 woman surfing\n1F3C4 200D 2640 - \; minimally-qualified # 🏄‍♀ E4.0 woman - surfing\n1F3C4 1F3FB 200D 2640 FE0F \; fully-qualified - # 🏄🏻‍♀️ E4.0 woman surfing: light skin tone\n1F3C4 1F3FB 200 - D 2640 \; minimally-qualified # 🏄🏻‍♀ E4.0 w - oman surfing: light skin tone\n1F3C4 1F3FC 200D 2640 FE0F - \; fully-qualified # 🏄🏼‍♀️ E4.0 woman surfing: medium-ligh - t skin tone\n1F3C4 1F3FC 200D 2640 \; minimally-quali - fied # 🏄🏼‍♀ E4.0 woman surfing: medium-light skin tone\n1F3C4 1F - 3FD 200D 2640 FE0F \; fully-qualified # 🏄🏽‍♀ - ️ E4.0 woman surfing: medium skin tone\n1F3C4 1F3FD 200D 2640 - \; minimally-qualified # 🏄🏽‍♀ E4.0 woman surfing: med - ium skin tone\n1F3C4 1F3FE 200D 2640 FE0F \; fully-qualifi - ed # 🏄🏾‍♀️ E4.0 woman surfing: medium-dark skin tone\n1F3C - 4 1F3FE 200D 2640 \; minimally-qualified # 🏄🏾 - ‍♀ E4.0 woman surfing: medium-dark skin tone\n1F3C4 1F3FF 200D 2640 FE - 0F \; fully-qualified # 🏄🏿‍♀️ E4.0 woman s - urfing: dark skin tone\n1F3C4 1F3FF 200D 2640 \; mini - mally-qualified # 🏄🏿‍♀ E4.0 woman surfing: dark skin tone\n1F6A3 - \; fully-qualified # 🚣 E2.0 p - erson rowing boat\n1F6A3 1F3FB \; fully-qua - lified # 🚣🏻 E2.0 person rowing boat: light skin tone\n1F6A3 1F3F - C \; fully-qualified # 🚣🏼 E2.0 pe - rson rowing boat: medium-light skin tone\n1F6A3 1F3FD - \; fully-qualified # 🚣🏽 E2.0 person rowing boat: medi - um skin tone\n1F6A3 1F3FE \; fully-qualifie - d # 🚣🏾 E2.0 person rowing boat: medium-dark skin tone\n1F6A3 1F3 - FF \; fully-qualified # 🚣�� E2.0 - person rowing boat: dark skin tone\n1F6A3 200D 2642 FE0F - \; fully-qualified # 🚣‍♂️ E4.0 man rowing boat\n1F6A3 2 - 00D 2642 \; minimally-qualified # 🚣‍♂ E4 - .0 man rowing boat\n1F6A3 1F3FB 200D 2642 FE0F \; fully-qu - alified # 🚣🏻‍♂️ E4.0 man rowing boat: light skin tone\n1F6 - A3 1F3FB 200D 2642 \; minimally-qualified # 🚣🏻 - ‍♂ E4.0 man rowing boat: light skin tone\n1F6A3 1F3FC 200D 2642 FE0F - \; fully-qualified # 🚣🏼‍♂️ E4.0 man rowing - boat: medium-light skin tone\n1F6A3 1F3FC 200D 2642 \ - ; minimally-qualified # 🚣🏼‍♂ E4.0 man rowing boat: medium-light - skin tone\n1F6A3 1F3FD 200D 2642 FE0F \; fully-qualified - # 🚣🏽‍♂️ E4.0 man rowing boat: medium skin tone\n1F6A3 1F3FD - 200D 2642 \; minimally-qualified # 🚣🏽‍♂ E4 - .0 man rowing boat: medium skin tone\n1F6A3 1F3FE 200D 2642 FE0F - \; fully-qualified # 🚣🏾‍♂️ E4.0 man rowing boat: me - dium-dark skin tone\n1F6A3 1F3FE 200D 2642 \; minimal - ly-qualified # 🚣🏾‍♂ E4.0 man rowing boat: medium-dark skin tone\ - n1F6A3 1F3FF 200D 2642 FE0F \; fully-qualified # 🚣 - 🏿‍♂️ E4.0 man rowing boat: dark skin tone\n1F6A3 1F3FF 200D 2642 - \; minimally-qualified # 🚣🏿‍♂ E4.0 man rowi - ng boat: dark skin tone\n1F6A3 200D 2640 FE0F \; ful - ly-qualified # 🚣‍♀️ E4.0 woman rowing boat\n1F6A3 200D 2640 - \; minimally-qualified # 🚣‍♀ E4.0 woman r - owing boat\n1F6A3 1F3FB 200D 2640 FE0F \; fully-qualified - # 🚣🏻‍♀️ E4.0 woman rowing boat: light skin tone\n1F6A3 1F3 - FB 200D 2640 \; minimally-qualified # 🚣🏻‍♀ - E4.0 woman rowing boat: light skin tone\n1F6A3 1F3FC 200D 2640 FE0F - \; fully-qualified # 🚣🏼‍♀️ E4.0 woman rowing boa - t: medium-light skin tone\n1F6A3 1F3FC 200D 2640 \; m - inimally-qualified # 🚣🏼‍♀ E4.0 woman rowing boat: medium-light s - kin tone\n1F6A3 1F3FD 200D 2640 FE0F \; fully-qualified - # 🚣🏽‍♀️ E4.0 woman rowing boat: medium skin tone\n1F6A3 1F3F - D 200D 2640 \; minimally-qualified # 🚣🏽‍♀ E - 4.0 woman rowing boat: medium skin tone\n1F6A3 1F3FE 200D 2640 FE0F - \; fully-qualified # 🚣🏾‍♀️ E4.0 woman rowing boa - t: medium-dark skin tone\n1F6A3 1F3FE 200D 2640 \; mi - nimally-qualified # 🚣🏾‍♀ E4.0 woman rowing boat: medium-dark ski - n tone\n1F6A3 1F3FF 200D 2640 FE0F \; fully-qualified - # 🚣🏿‍♀️ E4.0 woman rowing boat: dark skin tone\n1F6A3 1F3FF 20 - 0D 2640 \; minimally-qualified # 🚣🏿‍♀ E4.0 - woman rowing boat: dark skin tone\n1F3CA - \; fully-qualified # 🏊 E2.0 person swimming\n1F3CA 1F3FB - \; fully-qualified # 🏊🏻 E2.0 person swi - mming: light skin tone\n1F3CA 1F3FC \; full - y-qualified # 🏊🏼 E2.0 person swimming: medium-light skin tone\n1 - F3CA 1F3FD \; fully-qualified # 🏊 - 🏽 E2.0 person swimming: medium skin tone\n1F3CA 1F3FE - \; fully-qualified # 🏊🏾 E2.0 person swimming: medi - um-dark skin tone\n1F3CA 1F3FF \; fully-qua - lified # 🏊🏿 E2.0 person swimming: dark skin tone\n1F3CA 200D 264 - 2 FE0F \; fully-qualified # 🏊‍♂️ E4.0 m - an swimming\n1F3CA 200D 2642 \; minimally-quali - fied # 🏊‍♂ E4.0 man swimming\n1F3CA 1F3FB 200D 2642 FE0F - \; fully-qualified # 🏊🏻‍♂️ E4.0 man swimming: light - skin tone\n1F3CA 1F3FB 200D 2642 \; minimally-qualifi - ed # 🏊��‍♂ E4.0 man swimming: light skin tone\n1F3CA 1F3FC 200D - 2642 FE0F \; fully-qualified # 🏊🏼‍♂️ E4.0 - man swimming: medium-light skin tone\n1F3CA 1F3FC 200D 2642 - \; minimally-qualified # 🏊🏼‍♂ E4.0 man swimming: medium- - light skin tone\n1F3CA 1F3FD 200D 2642 FE0F \; fully-quali - fied # 🏊🏽‍♂️ E4.0 man swimming: medium skin tone\n1F3CA 1F - 3FD 200D 2642 \; minimally-qualified # 🏊🏽‍♂ - E4.0 man swimming: medium skin tone\n1F3CA 1F3FE 200D 2642 FE0F - \; fully-qualified # 🏊🏾‍♂️ E4.0 man swimming: mediu - m-dark skin tone\n1F3CA 1F3FE 200D 2642 \; minimally- - qualified # 🏊🏾‍♂ E4.0 man swimming: medium-dark skin tone\n1F3CA - 1F3FF 200D 2642 FE0F \; fully-qualified # 🏊🏿‍ - ♂️ E4.0 man swimming: dark skin tone\n1F3CA 1F3FF 200D 2642 - \; minimally-qualified # 🏊🏿‍♂ E4.0 man swimming: dark - skin tone\n1F3CA 200D 2640 FE0F \; fully-qualified - # 🏊‍♀️ E4.0 woman swimming\n1F3CA 200D 2640 - \; minimally-qualified # 🏊‍♀ E4.0 woman swimming\n1F3CA 1 - F3FB 200D 2640 FE0F \; fully-qualified # 🏊🏻‍ - ♀️ E4.0 woman swimming: light skin tone\n1F3CA 1F3FB 200D 2640 - \; minimally-qualified # 🏊🏻‍♀ E4.0 woman swimming: - light skin tone\n1F3CA 1F3FC 200D 2640 FE0F \; fully-qual - ified # 🏊🏼‍♀️ E4.0 woman swimming: medium-light skin tone\ - n1F3CA 1F3FC 200D 2640 \; minimally-qualified # 🏊 - 🏼‍♀ E4.0 woman swimming: medium-light skin tone\n1F3CA 1F3FD 200D 2 - 640 FE0F \; fully-qualified # 🏊🏽‍♀️ E4.0 w - oman swimming: medium skin tone\n1F3CA 1F3FD 200D 2640 - \; minimally-qualified # 🏊🏽‍♀ E4.0 woman swimming: medium skin - tone\n1F3CA 1F3FE 200D 2640 FE0F \; fully-qualified # - 🏊🏾‍♀️ E4.0 woman swimming: medium-dark skin tone\n1F3CA 1F3FE - 200D 2640 \; minimally-qualified # 🏊🏾‍♀ E4 - .0 woman swimming: medium-dark skin tone\n1F3CA 1F3FF 200D 2640 FE0F - \; fully-qualified # 🏊🏿‍♀️ E4.0 woman swimming: - dark skin tone\n1F3CA 1F3FF 200D 2640 \; minimally-q - ualified # 🏊🏿‍♀ E4.0 woman swimming: dark skin tone\n26F9 FE0F - \; fully-qualified # ⛹️ E2.0 perso - n bouncing ball\n26F9 \; unqualified - # ⛹ E2.0 person bouncing ball\n26F9 1F3FB - \; fully-qualified # ⛹🏻 E2.0 person bouncing ball: lig - ht skin tone\n26F9 1F3FC \; fully-qualifie - d # ⛹🏼 E2.0 person bouncing ball: medium-light skin tone\n26F9 1F - 3FD \; fully-qualified # ⛹🏽 E2.0 - person bouncing ball: medium skin tone\n26F9 1F3FE - \; fully-qualified # ⛹🏾 E2.0 person bouncing ball: mediu - m-dark skin tone\n26F9 1F3FF \; fully-qual - ified # ⛹🏿 E2.0 person bouncing ball: dark skin tone\n26F9 FE0F 2 - 00D 2642 FE0F \; fully-qualified # ⛹️‍♂️ E - 4.0 man bouncing ball\n26F9 200D 2642 FE0F \; unqua - lified # ⛹‍♂️ E4.0 man bouncing ball\n26F9 FE0F 200D 2642 - \; unqualified # ⛹️‍♂ E4.0 man boun - cing ball\n26F9 200D 2642 \; unqualified - # ⛹‍♂ E4.0 man bouncing ball\n26F9 1F3FB 200D 2642 FE0F - \; fully-qualified # ⛹🏻‍♂️ E4.0 man bouncing ball: - light skin tone\n26F9 1F3FB 200D 2642 \; minimally-q - ualified # ⛹🏻‍♂ E4.0 man bouncing ball: light skin tone\n26F9 1F3 - FC 200D 2642 FE0F \; fully-qualified # ⛹🏼‍♂ - ️ E4.0 man bouncing ball: medium-light skin tone\n26F9 1F3FC 200D 2642 - \; minimally-qualified # ⛹🏼‍♂ E4.0 man bounc - ing ball: medium-light skin tone\n26F9 1F3FD 200D 2642 FE0F - \; fully-qualified # ⛹🏽‍♂️ E4.0 man bouncing ball: mediu - m skin tone\n26F9 1F3FD 200D 2642 \; minimally-quali - fied # ⛹🏽‍♂ E4.0 man bouncing ball: medium skin tone\n26F9 1F3FE - 200D 2642 FE0F \; fully-qualified # ⛹🏾‍♂️ - E4.0 man bouncing ball: medium-dark skin tone\n26F9 1F3FE 200D 2642 - \; minimally-qualified # ⛹🏾‍♂ E4.0 man bouncing b - all: medium-dark skin tone\n26F9 1F3FF 200D 2642 FE0F \; - fully-qualified # ⛹🏿‍♂️ E4.0 man bouncing ball: dark skin t - one\n26F9 1F3FF 200D 2642 \; minimally-qualified # - ⛹🏿‍♂ E4.0 man bouncing ball: dark skin tone\n26F9 FE0F 200D 2640 - FE0F \; fully-qualified # ⛹️‍♀️ E4.0 woman - bouncing ball\n26F9 200D 2640 FE0F \; unqualified - # ⛹‍♀️ E4.0 woman bouncing ball\n26F9 FE0F 200D 2640 - \; unqualified # ⛹️‍♀ E4.0 woman bouncin - g ball\n26F9 200D 2640 \; unqualified - # ⛹‍♀ E4.0 woman bouncing ball\n26F9 1F3FB 200D 2640 FE0F - \; fully-qualified # ⛹🏻‍♀️ E4.0 woman bouncing ball: - light skin tone\n26F9 1F3FB 200D 2640 \; minimally- - qualified # ⛹🏻‍♀ E4.0 woman bouncing ball: light skin tone\n26F9 - 1F3FC 200D 2640 FE0F \; fully-qualified # ⛹🏼‍ - ♀️ E4.0 woman bouncing ball: medium-light skin tone\n26F9 1F3FC 200D 2 - 640 \; minimally-qualified # ⛹🏼‍♀ E4.0 woma - n bouncing ball: medium-light skin tone\n26F9 1F3FD 200D 2640 FE0F - \; fully-qualified # ⛹🏽‍♀️ E4.0 woman bouncing ba - ll: medium skin tone\n26F9 1F3FD 200D 2640 \; minima - lly-qualified # ⛹🏽‍♀ E4.0 woman bouncing ball: medium skin tone\n - 26F9 1F3FE 200D 2640 FE0F \; fully-qualified # ⛹ - 🏾‍♀️ E4.0 woman bouncing ball: medium-dark skin tone\n26F9 1F3FE - 200D 2640 \; minimally-qualified # ⛹🏾‍♀ E4. - 0 woman bouncing ball: medium-dark skin tone\n26F9 1F3FF 200D 2640 FE0F - \; fully-qualified # ⛹🏿‍♀️ E4.0 woman bounci - ng ball: dark skin tone\n26F9 1F3FF 200D 2640 \; min - imally-qualified # ⛹🏿‍♀ E4.0 woman bouncing ball: dark skin tone\ - n1F3CB FE0F \; fully-qualified # 🏋 - ️ E2.0 person lifting weights\n1F3CB - \; unqualified # 🏋 E2.0 person lifting weights\n1F3CB 1F3FB - \; fully-qualified # 🏋🏻 E2.0 perso - n lifting weights: light skin tone\n1F3CB 1F3FC - \; fully-qualified # 🏋🏼 E2.0 person lifting weights: medium - -light skin tone\n1F3CB 1F3FD \; fully-qual - ified # 🏋🏽 E2.0 person lifting weights: medium skin tone\n1F3CB - 1F3FE \; fully-qualified # 🏋🏾 E2. - 0 person lifting weights: medium-dark skin tone\n1F3CB 1F3FF - \; fully-qualified # 🏋🏿 E2.0 person lifting we - ights: dark skin tone\n1F3CB FE0F 200D 2642 FE0F \; fully - -qualified # 🏋️‍♂️ E4.0 man lifting weights\n1F3CB 200D 264 - 2 FE0F \; unqualified # 🏋‍♂️ E4.0 m - an lifting weights\n1F3CB FE0F 200D 2642 \; unqualif - ied # 🏋️‍♂ E4.0 man lifting weights\n1F3CB 200D 2642 - \; unqualified # 🏋‍♂ E4.0 man liftin - g weights\n1F3CB 1F3FB 200D 2642 FE0F \; fully-qualified - # 🏋🏻‍♂️ E4.0 man lifting weights: light skin tone\n1F3CB 1F - 3FB 200D 2642 \; minimally-qualified # 🏋🏻‍♂ - E4.0 man lifting weights: light skin tone\n1F3CB 1F3FC 200D 2642 FE0F - \; fully-qualified # 🏋🏼‍♂️ E4.0 man lifting w - eights: medium-light skin tone\n1F3CB 1F3FC 200D 2642 - \; minimally-qualified # 🏋🏼‍♂ E4.0 man lifting weights: medium- - light skin tone\n1F3CB 1F3FD 200D 2642 FE0F \; fully-quali - fied # 🏋🏽‍♂️ E4.0 man lifting weights: medium skin tone\n1 - F3CB 1F3FD 200D 2642 \; minimally-qualified # 🏋 - 🏽‍♂ E4.0 man lifting weights: medium skin tone\n1F3CB 1F3FE 200D 26 - 42 FE0F \; fully-qualified # 🏋🏾‍♂️ E4.0 ma - n lifting weights: medium-dark skin tone\n1F3CB 1F3FE 200D 2642 - \; minimally-qualified # 🏋🏾‍♂ E4.0 man lifting weight - s: medium-dark skin tone\n1F3CB 1F3FF 200D 2642 FE0F \; fu - lly-qualified # 🏋🏿‍♂️ E4.0 man lifting weights: dark skin - tone\n1F3CB 1F3FF 200D 2642 \; minimally-qualified # - 🏋🏿‍♂ E4.0 man lifting weights: dark skin tone\n1F3CB FE0F 200D 2 - 640 FE0F \; fully-qualified # 🏋️‍♀️ E4.0 w - oman lifting weights\n1F3CB 200D 2640 FE0F \; unqual - ified # 🏋‍♀️ E4.0 woman lifting weights\n1F3CB FE0F 200D - 2640 \; unqualified # 🏋️‍♀ E4.0 wom - an lifting weights\n1F3CB 200D 2640 \; unqualif - ied # 🏋‍♀ E4.0 woman lifting weights\n1F3CB 1F3FB 200D 2640 - FE0F \; fully-qualified # 🏋🏻‍♀️ E4.0 woma - n lifting weights: light skin tone\n1F3CB 1F3FB 200D 2640 - \; minimally-qualified # 🏋🏻‍♀ E4.0 woman lifting weights: l - ight skin tone\n1F3CB 1F3FC 200D 2640 FE0F \; fully-qualif - ied # 🏋🏼‍♀️ E4.0 woman lifting weights: medium-light skin - tone\n1F3CB 1F3FC 200D 2640 \; minimally-qualified # - 🏋🏼‍♀ E4.0 woman lifting weights: medium-light skin tone\n1F3CB 1 - F3FD 200D 2640 FE0F \; fully-qualified # 🏋🏽‍ - ♀️ E4.0 woman lifting weights: medium skin tone\n1F3CB 1F3FD 200D 2640 - \; minimally-qualified # 🏋🏽‍♀ E4.0 woman l - ifting weights: medium skin tone\n1F3CB 1F3FE 200D 2640 FE0F - \; fully-qualified # 🏋��‍♀️ E4.0 woman lifting weights - : medium-dark skin tone\n1F3CB 1F3FE 200D 2640 \; min - imally-qualified # 🏋🏾‍♀ E4.0 woman lifting weights: medium-dark - skin tone\n1F3CB 1F3FF 200D 2640 FE0F \; fully-qualified - # 🏋🏿‍♀️ E4.0 woman lifting weights: dark skin tone\n1F3CB 1 - F3FF 200D 2640 \; minimally-qualified # 🏋🏿‍ - ♀ E4.0 woman lifting weights: dark skin tone\n1F6B4 - \; fully-qualified # 🚴 E2.0 person biking\n1F6B4 1 - F3FB \; fully-qualified # 🚴🏻 E2.0 - person biking: light skin tone\n1F6B4 1F3FC - \; fully-qualified # 🚴🏼 E2.0 person biking: medium-light skin - tone\n1F6B4 1F3FD \; fully-qualified # - 🚴🏽 E2.0 person biking: medium skin tone\n1F6B4 1F3FE - \; fully-qualified # 🚴🏾 E2.0 person biking: medi - um-dark skin tone\n1F6B4 1F3FF \; fully-qua - lified # 🚴🏿 E2.0 person biking: dark skin tone\n1F6B4 200D 2642 - FE0F \; fully-qualified # 🚴‍♂️ E4.0 man - biking\n1F6B4 200D 2642 \; minimally-qualified - # 🚴‍♂ E4.0 man biking\n1F6B4 1F3FB 200D 2642 FE0F - \; fully-qualified # 🚴🏻‍♂️ E4.0 man biking: light skin ton - e\n1F6B4 1F3FB 200D 2642 \; minimally-qualified # - 🚴🏻‍♂ E4.0 man biking: light skin tone\n1F6B4 1F3FC 200D 2642 FE0 - F \; fully-qualified # 🚴🏼‍♂️ E4.0 man biki - ng: medium-light skin tone\n1F6B4 1F3FC 200D 2642 \; - minimally-qualified # 🚴🏼‍♂ E4.0 man biking: medium-light skin to - ne\n1F6B4 1F3FD 200D 2642 FE0F \; fully-qualified # - 🚴🏽‍♂️ E4.0 man biking: medium skin tone\n1F6B4 1F3FD 200D 2642 - \; minimally-qualified # 🚴🏽‍♂ E4.0 man bik - ing: medium skin tone\n1F6B4 1F3FE 200D 2642 FE0F \; fully - -qualified # 🚴🏾‍♂️ E4.0 man biking: medium-dark skin tone\ - n1F6B4 1F3FE 200D 2642 \; minimally-qualified # 🚴 - 🏾‍♂ E4.0 man biking: medium-dark skin tone\n1F6B4 1F3FF 200D 2642 F - E0F \; fully-qualified # 🚴🏿‍♂️ E4.0 man bi - king: dark skin tone\n1F6B4 1F3FF 200D 2642 \; minima - lly-qualified # 🚴🏿‍♂ E4.0 man biking: dark skin tone\n1F6B4 200D - 2640 FE0F \; fully-qualified # 🚴‍♀️ E4 - .0 woman biking\n1F6B4 200D 2640 \; minimally-q - ualified # 🚴‍♀ E4.0 woman biking\n1F6B4 1F3FB 200D 2640 FE0F - \; fully-qualified # 🚴🏻‍♀️ E4.0 woman biking: li - ght skin tone\n1F6B4 1F3FB 200D 2640 \; minimally-qua - lified # 🚴🏻‍♀ E4.0 woman biking: light skin tone\n1F6B4 1F3FC 20 - 0D 2640 FE0F \; fully-qualified # 🚴🏼‍♀️ E4 - .0 woman biking: medium-light skin tone\n1F6B4 1F3FC 200D 2640 - \; minimally-qualified # 🚴🏼‍♀ E4.0 woman biking: mediu - m-light skin tone\n1F6B4 1F3FD 200D 2640 FE0F \; fully-qua - lified # 🚴🏽‍♀️ E4.0 woman biking: medium skin tone\n1F6B4 - 1F3FD 200D 2640 \; minimally-qualified # 🚴🏽‍ - ♀ E4.0 woman biking: medium skin tone\n1F6B4 1F3FE 200D 2640 FE0F - \; fully-qualified # 🚴🏾‍♀️ E4.0 woman biking: me - dium-dark skin tone\n1F6B4 1F3FE 200D 2640 \; minimal - ly-qualified # 🚴🏾‍♀ E4.0 woman biking: medium-dark skin tone\n1F - 6B4 1F3FF 200D 2640 FE0F \; fully-qualified # 🚴🏿 - ‍♀️ E4.0 woman biking: dark skin tone\n1F6B4 1F3FF 200D 2640 - \; minimally-qualified # 🚴🏿‍♀ E4.0 woman biking: d - ark skin tone\n1F6B5 \; fully-qualifi - ed # 🚵 E2.0 person mountain biking\n1F6B5 1F3FB - \; fully-qualified # 🚵🏻 E2.0 person mountain biking: - light skin tone\n1F6B5 1F3FC \; fully-qual - ified # 🚵🏼 E2.0 person mountain biking: medium-light skin tone\n - 1F6B5 1F3FD \; fully-qualified # 🚵 - 🏽 E2.0 person mountain biking: medium skin tone\n1F6B5 1F3FE - \; fully-qualified # 🚵🏾 E2.0 person mountai - n biking: medium-dark skin tone\n1F6B5 1F3FF - \; fully-qualified # 🚵🏿 E2.0 person mountain biking: dark skin - tone\n1F6B5 200D 2642 FE0F \; fully-qualified # - 🚵‍♂️ E4.0 man mountain biking\n1F6B5 200D 2642 - \; minimally-qualified # 🚵‍♂ E4.0 man mountain biking\n1F - 6B5 1F3FB 200D 2642 FE0F \; fully-qualified # 🚵🏻 - ‍♂️ E4.0 man mountain biking: light skin tone\n1F6B5 1F3FB 200D 2642 - \; minimally-qualified # 🚵��‍♂ E4.0 man m - ountain biking: light skin tone\n1F6B5 1F3FC 200D 2642 FE0F - \; fully-qualified # 🚵🏼‍♂️ E4.0 man mountain biking: med - ium-light skin tone\n1F6B5 1F3FC 200D 2642 \; minimal - ly-qualified # 🚵🏼‍♂ E4.0 man mountain biking: medium-light skin - tone\n1F6B5 1F3FD 200D 2642 FE0F \; fully-qualified # - 🚵🏽‍♂️ E4.0 man mountain biking: medium skin tone\n1F6B5 1F3FD - 200D 2642 \; minimally-qualified # 🚵🏽‍♂ E4. - 0 man mountain biking: medium skin tone\n1F6B5 1F3FE 200D 2642 FE0F - \; fully-qualified # 🚵🏾‍♂️ E4.0 man mountain bik - ing: medium-dark skin tone\n1F6B5 1F3FE 200D 2642 \; - minimally-qualified # 🚵🏾‍♂ E4.0 man mountain biking: medium-dark - skin tone\n1F6B5 1F3FF 200D 2642 FE0F \; fully-qualified - # 🚵🏿‍♂️ E4.0 man mountain biking: dark skin tone\n1F6B5 1F - 3FF 200D 2642 \; minimally-qualified # 🚵��‍ - ♂ E4.0 man mountain biking: dark skin tone\n1F6B5 200D 2640 FE0F - \; fully-qualified # 🚵‍♀️ E4.0 woman mountain - biking\n1F6B5 200D 2640 \; minimally-qualified - # 🚵‍♀ E4.0 woman mountain biking\n1F6B5 1F3FB 200D 2640 FE0F - \; fully-qualified # 🚵🏻‍♀️ E4.0 woman mountain b - iking: light skin tone\n1F6B5 1F3FB 200D 2640 \; mini - mally-qualified # 🚵🏻‍♀ E4.0 woman mountain biking: light skin to - ne\n1F6B5 1F3FC 200D 2640 FE0F \; fully-qualified # - 🚵🏼‍♀️ E4.0 woman mountain biking: medium-light skin tone\n1F6B - 5 1F3FC 200D 2640 \; minimally-qualified # 🚵🏼 - ‍♀ E4.0 woman mountain biking: medium-light skin tone\n1F6B5 1F3FD 200 - D 2640 FE0F \; fully-qualified # 🚵🏽‍♀️ E4. - 0 woman mountain biking: medium skin tone\n1F6B5 1F3FD 200D 2640 - \; minimally-qualified # 🚵🏽‍♀ E4.0 woman mountain bi - king: medium skin tone\n1F6B5 1F3FE 200D 2640 FE0F \; full - y-qualified # 🚵🏾‍♀️ E4.0 woman mountain biking: medium-dar - k skin tone\n1F6B5 1F3FE 200D 2640 \; minimally-quali - fied # 🚵🏾‍♀ E4.0 woman mountain biking: medium-dark skin tone\n1 - F6B5 1F3FF 200D 2640 FE0F \; fully-qualified # 🚵 - 🏿‍♀️ E4.0 woman mountain biking: dark skin tone\n1F6B5 1F3FF 200D - 2640 \; minimally-qualified # 🚵🏿‍♀ E4.0 wo - man mountain biking: dark skin tone\n1F938 - \; fully-qualified # 🤸 E4.0 person cartwheeling\n1F938 1F3FB - \; fully-qualified # 🤸🏻 E4.0 pers - on cartwheeling: light skin tone\n1F938 1F3FC - \; fully-qualified # 🤸🏼 E4.0 person cartwheeling: medium-ligh - t skin tone\n1F938 1F3FD \; fully-qualified - # 🤸🏽 E4.0 person cartwheeling: medium skin tone\n1F938 1F3FE - \; fully-qualified # 🤸🏾 E4.0 person - cartwheeling: medium-dark skin tone\n1F938 1F3FF - \; fully-qualified # 🤸🏿 E4.0 person cartwheeling: dark sk - in tone\n1F938 200D 2642 FE0F \; fully-qualified - # 🤸‍♂️ E4.0 man cartwheeling\n1F938 200D 2642 - \; minimally-qualified # 🤸‍♂ E4.0 man cartwheeling\n1F938 - 1F3FB 200D 2642 FE0F \; fully-qualified # 🤸🏻‍ - ♂️ E4.0 man cartwheeling: light skin tone\n1F938 1F3FB 200D 2642 - \; minimally-qualified # 🤸��‍♂ E4.0 man cartwhe - eling: light skin tone\n1F938 1F3FC 200D 2642 FE0F \; full - y-qualified # 🤸🏼‍♂️ E4.0 man cartwheeling: medium-light sk - in tone\n1F938 1F3FC 200D 2642 \; minimally-qualified - # 🤸🏼‍♂ E4.0 man cartwheeling: medium-light skin tone\n1F938 1F3 - FD 200D 2642 FE0F \; fully-qualified # 🤸🏽‍♂ - ️ E4.0 man cartwheeling: medium skin tone\n1F938 1F3FD 200D 2642 - \; minimally-qualified # 🤸🏽‍♂ E4.0 man cartwheelin - g: medium skin tone\n1F938 1F3FE 200D 2642 FE0F \; fully-q - ualified # 🤸🏾‍♂️ E4.0 man cartwheeling: medium-dark skin t - one\n1F938 1F3FE 200D 2642 \; minimally-qualified # - 🤸🏾‍♂ E4.0 man cartwheeling: medium-dark skin tone\n1F938 1F3FF 2 - 00D 2642 FE0F \; fully-qualified # 🤸🏿‍♂️ E - 4.0 man cartwheeling: dark skin tone\n1F938 1F3FF 200D 2642 - \; minimally-qualified # 🤸🏿‍♂ E4.0 man cartwheeling: dark - skin tone\n1F938 200D 2640 FE0F \; fully-qualified - # 🤸‍♀️ E4.0 woman cartwheeling\n1F938 200D 2640 - \; minimally-qualified # 🤸‍♀ E4.0 woman cartwheeling\ - n1F938 1F3FB 200D 2640 FE0F \; fully-qualified # 🤸 - 🏻‍♀️ E4.0 woman cartwheeling: light skin tone\n1F938 1F3FB 200D 2 - 640 \; minimally-qualified # 🤸🏻‍♀ E4.0 woma - n cartwheeling: light skin tone\n1F938 1F3FC 200D 2640 FE0F - \; fully-qualified # 🤸🏼‍♀️ E4.0 woman cartwheeling: medi - um-light skin tone\n1F938 1F3FC 200D 2640 \; minimall - y-qualified # 🤸🏼‍♀ E4.0 woman cartwheeling: medium-light skin to - ne\n1F938 1F3FD 200D 2640 FE0F \; fully-qualified # - 🤸🏽‍♀️ E4.0 woman cartwheeling: medium skin tone\n1F938 1F3FD 2 - 00D 2640 \; minimally-qualified # 🤸🏽‍♀ E4.0 - woman cartwheeling: medium skin tone\n1F938 1F3FE 200D 2640 FE0F - \; fully-qualified # 🤸🏾‍♀️ E4.0 woman cartwheeling - : medium-dark skin tone\n1F938 1F3FE 200D 2640 \; min - imally-qualified # 🤸🏾‍♀ E4.0 woman cartwheeling: medium-dark ski - n tone\n1F938 1F3FF 200D 2640 FE0F \; fully-qualified - # 🤸🏿‍♀️ E4.0 woman cartwheeling: dark skin tone\n1F938 1F3FF 2 - 00D 2640 \; minimally-qualified # 🤸🏿‍♀ E4.0 - woman cartwheeling: dark skin tone\n1F93C - \; fully-qualified # 🤼 E4.0 people wrestling\n1F93C 200D 2642 - FE0F \; fully-qualified # 🤼‍♂️ E4.0 me - n wrestling\n1F93C 200D 2642 \; minimally-quali - fied # 🤼‍♂ E4.0 men wrestling\n1F93C 200D 2640 FE0F - \; fully-qualified # 🤼‍♀️ E4.0 women wrestling\n1F93C - 200D 2640 \; minimally-qualified # 🤼‍♀ E - 4.0 women wrestling\n1F93D \; fully-q - ualified # 🤽 E4.0 person playing water polo\n1F93D 1F3FB - \; fully-qualified # 🤽🏻 E4.0 person playing - water polo: light skin tone\n1F93D 1F3FC \ - ; fully-qualified # 🤽🏼 E4.0 person playing water polo: medium-li - ght skin tone\n1F93D 1F3FD \; fully-qualifi - ed # 🤽🏽 E4.0 person playing water polo: medium skin tone\n1F93D - 1F3FE \; fully-qualified # 🤽🏾 E4. - 0 person playing water polo: medium-dark skin tone\n1F93D 1F3FF - \; fully-qualified # 🤽🏿 E4.0 person playing - water polo: dark skin tone\n1F93D 200D 2642 FE0F \; - fully-qualified # 🤽‍♂️ E4.0 man playing water polo\n1F93D 20 - 0D 2642 \; minimally-qualified # 🤽‍♂ E4. - 0 man playing water polo\n1F93D 1F3FB 200D 2642 FE0F \; fu - lly-qualified # 🤽🏻‍♂️ E4.0 man playing water polo: light s - kin tone\n1F93D 1F3FB 200D 2642 \; minimally-qualifie - d # 🤽🏻‍♂ E4.0 man playing water polo: light skin tone\n1F93D 1F3 - FC 200D 2642 FE0F \; fully-qualified # 🤽🏼‍♂ - ️ E4.0 man playing water polo: medium-light skin tone\n1F93D 1F3FC 200D - 2642 \; minimally-qualified # 🤽🏼‍♂ E4.0 man - playing water polo: medium-light skin tone\n1F93D 1F3FD 200D 2642 FE0F - \; fully-qualified # 🤽🏽‍♂️ E4.0 man playing - water polo: medium skin tone\n1F93D 1F3FD 200D 2642 \ - ; minimally-qualified # 🤽🏽‍♂ E4.0 man playing water polo: medium - skin tone\n1F93D 1F3FE 200D 2642 FE0F \; fully-qualified - # 🤽🏾‍♂️ E4.0 man playing water polo: medium-dark skin tone - \n1F93D 1F3FE 200D 2642 \; minimally-qualified # 🤽 - 🏾‍♂ E4.0 man playing water polo: medium-dark skin tone\n1F93D 1F3FF - 200D 2642 FE0F \; fully-qualified # 🤽🏿‍♂️ - E4.0 man playing water polo: dark skin tone\n1F93D 1F3FF 200D 2642 - \; minimally-qualified # 🤽🏿‍♂ E4.0 man playing wa - ter polo: dark skin tone\n1F93D 200D 2640 FE0F \; fu - lly-qualified # 🤽‍♀️ E4.0 woman playing water polo\n1F93D 200 - D 2640 \; minimally-qualified # 🤽‍♀ E4.0 - woman playing water polo\n1F93D 1F3FB 200D 2640 FE0F \; f - ully-qualified # 🤽🏻‍♀️ E4.0 woman playing water polo: ligh - t skin tone\n1F93D 1F3FB 200D 2640 \; minimally-quali - fied # 🤽🏻‍♀ E4.0 woman playing water polo: light skin tone\n1F93 - D 1F3FC 200D 2640 FE0F \; fully-qualified # 🤽🏼 - ‍♀️ E4.0 woman playing water polo: medium-light skin tone\n1F93D 1F3 - FC 200D 2640 \; minimally-qualified # 🤽🏼‍♀ - E4.0 woman playing water polo: medium-light skin tone\n1F93D 1F3FD 200D 26 - 40 FE0F \; fully-qualified # 🤽🏽‍♀️ E4.0 wo - man playing water polo: medium skin tone\n1F93D 1F3FD 200D 2640 - \; minimally-qualified # 🤽🏽‍♀ E4.0 woman playing wate - r polo: medium skin tone\n1F93D 1F3FE 200D 2640 FE0F \; fu - lly-qualified # 🤽🏾‍♀️ E4.0 woman playing water polo: mediu - m-dark skin tone\n1F93D 1F3FE 200D 2640 \; minimally- - qualified # 🤽🏾‍♀ E4.0 woman playing water polo: medium-dark skin - tone\n1F93D 1F3FF 200D 2640 FE0F \; fully-qualified # - 🤽🏿‍♀️ E4.0 woman playing water polo: dark skin tone\n1F93D 1F - 3FF 200D 2640 \; minimally-qualified # 🤽🏿‍♀ - E4.0 woman playing water polo: dark skin tone\n1F93E - \; fully-qualified # 🤾 E4.0 person playing handbal - l\n1F93E 1F3FB \; fully-qualified # - 🤾🏻 E4.0 person playing handball: light skin tone\n1F93E 1F3FC - \; fully-qualified # 🤾🏼 E4.0 person pla - ying handball: medium-light skin tone\n1F93E 1F3FD - \; fully-qualified # 🤾🏽 E4.0 person playing handball: me - dium skin tone\n1F93E 1F3FE \; fully-qualif - ied # 🤾🏾 E4.0 person playing handball: medium-dark skin tone\n1F - 93E 1F3FF \; fully-qualified # 🤾🏿 - E4.0 person playing handball: dark skin tone\n1F93E 200D 2642 FE0F - \; fully-qualified # 🤾‍♂️ E4.0 man playing ha - ndball\n1F93E 200D 2642 \; minimally-qualified - # 🤾‍♂ E4.0 man playing handball\n1F93E 1F3FB 200D 2642 FE0F - \; fully-qualified # 🤾🏻‍♂️ E4.0 man playing handb - all: light skin tone\n1F93E 1F3FB 200D 2642 \; minima - lly-qualified # 🤾🏻‍♂ E4.0 man playing handball: light skin tone\ - n1F93E 1F3FC 200D 2642 FE0F \; fully-qualified # 🤾 - 🏼‍♂️ E4.0 man playing handball: medium-light skin tone\n1F93E 1F3 - FC 200D 2642 \; minimally-qualified # 🤾🏼‍♂ - E4.0 man playing handball: medium-light skin tone\n1F93E 1F3FD 200D 2642 F - E0F \; fully-qualified # 🤾🏽‍♂️ E4.0 man pl - aying handball: medium skin tone\n1F93E 1F3FD 200D 2642 - \; minimally-qualified # 🤾🏽‍♂ E4.0 man playing handball: medi - um skin tone\n1F93E 1F3FE 200D 2642 FE0F \; fully-qualifie - d # 🤾🏾‍♂️ E4.0 man playing handball: medium-dark skin tone - \n1F93E 1F3FE 200D 2642 \; minimally-qualified # 🤾 - 🏾‍♂ E4.0 man playing handball: medium-dark skin tone\n1F93E 1F3FF 2 - 00D 2642 FE0F \; fully-qualified # 🤾🏿‍♂️ E - 4.0 man playing handball: dark skin tone\n1F93E 1F3FF 200D 2642 - \; minimally-qualified # 🤾🏿‍♂ E4.0 man playing handba - ll: dark skin tone\n1F93E 200D 2640 FE0F \; fully-qu - alified # 🤾‍♀️ E4.0 woman playing handball\n1F93E 200D 2640 - \; minimally-qualified # 🤾‍♀ E4.0 woman p - laying handball\n1F93E 1F3FB 200D 2640 FE0F \; fully-quali - fied # 🤾🏻‍♀️ E4.0 woman playing handball: light skin tone\ - n1F93E 1F3FB 200D 2640 \; minimally-qualified # 🤾 - 🏻‍♀ E4.0 woman playing handball: light skin tone\n1F93E 1F3FC 200D - 2640 FE0F \; fully-qualified # 🤾🏼‍♀️ E4.0 - woman playing handball: medium-light skin tone\n1F93E 1F3FC 200D 2640 - \; minimally-qualified # 🤾🏼‍♀ E4.0 woman playin - g handball: medium-light skin tone\n1F93E 1F3FD 200D 2640 FE0F - \; fully-qualified # 🤾🏽‍♀️ E4.0 woman playing handbal - l: medium skin tone\n1F93E 1F3FD 200D 2640 \; minimal - ly-qualified # 🤾🏽‍♀ E4.0 woman playing handball: medium skin ton - e\n1F93E 1F3FE 200D 2640 FE0F \; fully-qualified # - 🤾🏾‍♀️ E4.0 woman playing handball: medium-dark skin tone\n1F93 - E 1F3FE 200D 2640 \; minimally-qualified # 🤾🏾 - ‍♀ E4.0 woman playing handball: medium-dark skin tone\n1F93E 1F3FF 200 - D 2640 FE0F \; fully-qualified # 🤾🏿‍♀️ E4. - 0 woman playing handball: dark skin tone\n1F93E 1F3FF 200D 2640 - \; minimally-qualified # 🤾🏿‍♀ E4.0 woman playing hand - ball: dark skin tone\n1F939 \; fully- - qualified # 🤹 E4.0 person juggling\n1F939 1F3FB - \; fully-qualified # 🤹🏻 E4.0 person juggling: light - skin tone\n1F939 1F3FC \; fully-qualified - # 🤹🏼 E4.0 person juggling: medium-light skin tone\n1F939 1F3FD - \; fully-qualified # 🤹🏽 E4.0 person - juggling: medium skin tone\n1F939 1F3FE \; - fully-qualified # 🤹🏾 E4.0 person juggling: medium-dark skin ton - e\n1F939 1F3FF \; fully-qualified # - 🤹🏿 E4.0 person juggling: dark skin tone\n1F939 200D 2642 FE0F - \; fully-qualified # 🤹‍♂️ E4.0 man juggling\n - 1F939 200D 2642 \; minimally-qualified # 🤹 - ‍♂ E4.0 man juggling\n1F939 1F3FB 200D 2642 FE0F \; fu - lly-qualified # 🤹🏻‍♂️ E4.0 man juggling: light skin tone\n - 1F939 1F3FB 200D 2642 \; minimally-qualified # 🤹 - 🏻‍♂ E4.0 man juggling: light skin tone\n1F939 1F3FC 200D 2642 FE0F - \; fully-qualified # 🤹🏼‍♂️ E4.0 man juggli - ng: medium-light skin tone\n1F939 1F3FC 200D 2642 \; - minimally-qualified # 🤹🏼‍♂ E4.0 man juggling: medium-light skin - tone\n1F939 1F3FD 200D 2642 FE0F \; fully-qualified # - 🤹🏽‍♂️ E4.0 man juggling: medium skin tone\n1F939 1F3FD 200D 26 - 42 \; minimally-qualified # 🤹🏽‍♂ E4.0 man j - uggling: medium skin tone\n1F939 1F3FE 200D 2642 FE0F \; f - ully-qualified # 🤹🏾‍♂️ E4.0 man juggling: medium-dark skin - tone\n1F939 1F3FE 200D 2642 \; minimally-qualified # - 🤹🏾‍♂ E4.0 man juggling: medium-dark skin tone\n1F939 1F3FF 200D - 2642 FE0F \; fully-qualified # 🤹🏿‍♂️ E4.0 - man juggling: dark skin tone\n1F939 1F3FF 200D 2642 - \; minimally-qualified # 🤹🏿‍♂ E4.0 man juggling: dark skin tone\ - n1F939 200D 2640 FE0F \; fully-qualified # 🤹 - ‍♀️ E4.0 woman juggling\n1F939 200D 2640 - \; minimally-qualified # 🤹‍♀ E4.0 woman juggling\n1F939 1F3FB 200D - 2640 FE0F \; fully-qualified # 🤹🏻‍♀️ E4.0 - woman juggling: light skin tone\n1F939 1F3FB 200D 2640 - \; minimally-qualified # 🤹🏻‍♀ E4.0 woman juggling: light skin - tone\n1F939 1F3FC 200D 2640 FE0F \; fully-qualified # - 🤹🏼‍♀️ E4.0 woman juggling: medium-light skin tone\n1F939 1F3FC - 200D 2640 \; minimally-qualified # 🤹🏼‍♀ E4 - .0 woman juggling: medium-light skin tone\n1F939 1F3FD 200D 2640 FE0F - \; fully-qualified # 🤹🏽‍♀️ E4.0 woman juggling - : medium skin tone\n1F939 1F3FD 200D 2640 \; minimall - y-qualified # 🤹🏽‍♀ E4.0 woman juggling: medium skin tone\n1F939 - 1F3FE 200D 2640 FE0F \; fully-qualified # 🤹🏾‍ - ♀️ E4.0 woman juggling: medium-dark skin tone\n1F939 1F3FE 200D 2640 - \; minimally-qualified # 🤹🏾‍♀ E4.0 woman jug - gling: medium-dark skin tone\n1F939 1F3FF 200D 2640 FE0F \ - ; fully-qualified # 🤹🏿‍♀️ E4.0 woman juggling: dark skin t - one\n1F939 1F3FF 200D 2640 \; minimally-qualified # - 🤹🏿‍♀ E4.0 woman juggling: dark skin tone\n\n# subgroup: person-r - esting\n1F9D8 \; fully-qualified - # 🧘 E5.0 person in lotus position\n1F9D8 1F3FB - \; fully-qualified # 🧘🏻 E5.0 person in lotus position: li - ght skin tone\n1F9D8 1F3FC \; fully-qualifi - ed # 🧘🏼 E5.0 person in lotus position: medium-light skin tone\n1 - F9D8 1F3FD \; fully-qualified # 🧘 - 🏽 E5.0 person in lotus position: medium skin tone\n1F9D8 1F3FE - \; fully-qualified # 🧘🏾 E5.0 person in lo - tus position: medium-dark skin tone\n1F9D8 1F3FF - \; fully-qualified # 🧘🏿 E5.0 person in lotus position: dar - k skin tone\n1F9D8 200D 2642 FE0F \; fully-qualified - # 🧘‍♂️ E5.0 man in lotus position\n1F9D8 200D 2642 - \; minimally-qualified # 🧘‍♂ E5.0 man in lotus po - sition\n1F9D8 1F3FB 200D 2642 FE0F \; fully-qualified - # 🧘🏻‍♂️ E5.0 man in lotus position: light skin tone\n1F9D8 1F3 - FB 200D 2642 \; minimally-qualified # 🧘🏻‍♂ - E5.0 man in lotus position: light skin tone\n1F9D8 1F3FC 200D 2642 FE0F - \; fully-qualified # 🧘🏼‍♂️ E5.0 man in lotus - position: medium-light skin tone\n1F9D8 1F3FC 200D 2642 - \; minimally-qualified # 🧘🏼‍♂ E5.0 man in lotus position: me - dium-light skin tone\n1F9D8 1F3FD 200D 2642 FE0F \; fully- - qualified # 🧘🏽‍♂️ E5.0 man in lotus position: medium skin - tone\n1F9D8 1F3FD 200D 2642 \; minimally-qualified # - 🧘🏽‍♂ E5.0 man in lotus position: medium skin tone\n1F9D8 1F3FE 2 - 00D 2642 FE0F \; fully-qualified # 🧘🏾‍♂️ E - 5.0 man in lotus position: medium-dark skin tone\n1F9D8 1F3FE 200D 2642 - \; minimally-qualified # 🧘🏾‍♂ E5.0 man in lot - us position: medium-dark skin tone\n1F9D8 1F3FF 200D 2642 FE0F - \; fully-qualified # 🧘🏿‍♂️ E5.0 man in lotus position - : dark skin tone\n1F9D8 1F3FF 200D 2642 \; minimally- - qualified # 🧘🏿‍♂ E5.0 man in lotus position: dark skin tone\n1F9 - D8 200D 2640 FE0F \; fully-qualified # 🧘‍ - ♀️ E5.0 woman in lotus position\n1F9D8 200D 2640 - \; minimally-qualified # 🧘‍♀ E5.0 woman in lotus position\n1F - 9D8 1F3FB 200D 2640 FE0F \; fully-qualified # 🧘🏻 - ‍♀️ E5.0 woman in lotus position: light skin tone\n1F9D8 1F3FB 200D - 2640 \; minimally-qualified # 🧘🏻‍♀ E5.0 wom - an in lotus position: light skin tone\n1F9D8 1F3FC 200D 2640 FE0F - \; fully-qualified # 🧘🏼‍♀️ E5.0 woman in lotus pos - ition: medium-light skin tone\n1F9D8 1F3FC 200D 2640 - \; minimally-qualified # 🧘🏼‍♀ E5.0 woman in lotus position: medi - um-light skin tone\n1F9D8 1F3FD 200D 2640 FE0F \; fully-qu - alified # 🧘🏽‍♀️ E5.0 woman in lotus position: medium skin - tone\n1F9D8 1F3FD 200D 2640 \; minimally-qualified # - 🧘🏽‍♀ E5.0 woman in lotus position: medium skin tone\n1F9D8 1F3FE - 200D 2640 FE0F \; fully-qualified # 🧘🏾‍♀️ - E5.0 woman in lotus position: medium-dark skin tone\n1F9D8 1F3FE 200D 264 - 0 \; minimally-qualified # 🧘🏾‍♀ E5.0 woman - in lotus position: medium-dark skin tone\n1F9D8 1F3FF 200D 2640 FE0F - \; fully-qualified # 🧘🏿‍♀️ E5.0 woman in lotus - position: dark skin tone\n1F9D8 1F3FF 200D 2640 \; mi - nimally-qualified # 🧘🏿‍♀ E5.0 woman in lotus position: dark skin - tone\n1F6C0 \; fully-qualified # - 🛀 E2.0 person taking bath\n1F6C0 1F3FB - \; fully-qualified # 🛀🏻 E2.0 person taking bath: light skin tone - \n1F6C0 1F3FC \; fully-qualified # 🛀 - 🏼 E2.0 person taking bath: medium-light skin tone\n1F6C0 1F3FD - \; fully-qualified # 🛀🏽 E2.0 person takin - g bath: medium skin tone\n1F6C0 1F3FE \; fu - lly-qualified # 🛀🏾 E2.0 person taking bath: medium-dark skin ton - e\n1F6C0 1F3FF \; fully-qualified # - 🛀🏿 E2.0 person taking bath: dark skin tone\n1F6CC - \; fully-qualified # 🛌 E2.0 person in bed\n1F6CC - 1F3FB \; fully-qualified # 🛌🏻 E4 - .0 person in bed: light skin tone\n1F6CC 1F3FC - \; fully-qualified # 🛌🏼 E4.0 person in bed: medium-light ski - n tone\n1F6CC 1F3FD \; fully-qualified - # 🛌🏽 E4.0 person in bed: medium skin tone\n1F6CC 1F3FE - \; fully-qualified # 🛌🏾 E4.0 person in bed: me - dium-dark skin tone\n1F6CC 1F3FF \; fully-q - ualified # 🛌🏿 E4.0 person in bed: dark skin tone\n\n# subgroup: - family\n1F9D1 200D 1F91D 200D 1F9D1 \; fully-qualified - # 🧑‍🤝‍🧑 E12.1 people holding hands\n1F9D1 1F3FB 200D 1F91D 20 - 0D 1F9D1 1F3FB \; fully-qualified # 🧑🏻‍🤝‍🧑🏻 E12. - 1 people holding hands: light skin tone\n1F9D1 1F3FB 200D 1F91D 200D 1F9D1 - 1F3FC \; fully-qualified # 🧑🏻‍🤝‍🧑🏼 E12.1 people - holding hands: light skin tone\, medium-light skin tone\n1F9D1 1F3FB 200D - 1F91D 200D 1F9D1 1F3FD \; fully-qualified # 🧑🏻‍🤝‍🧑 - 🏽 E12.1 people holding hands: light skin tone\, medium skin tone\n1F9D1 - 1F3FB 200D 1F91D 200D 1F9D1 1F3FE \; fully-qualified # 🧑🏻‍ - 🤝‍🧑🏾 E12.1 people holding hands: light skin tone\, medium-dark - skin tone\n1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FF \; fully-qualified - # 🧑🏻‍🤝‍🧑🏿 E12.1 people holding hands: light skin ton - e\, dark skin tone\n1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FB \; fully-qu - alified # 🧑🏼‍🤝‍🧑🏻 E12.1 people holding hands: mediu - m-light skin tone\, light skin tone\n1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3 - FC \; fully-qualified # 🧑🏼‍🤝‍🧑🏼 E12.1 people hol - ding hands: medium-light skin tone\n1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3F - D \; fully-qualified # 🧑🏼‍🤝‍🧑🏽 E12.1 people hold - ing hands: medium-light skin tone\, medium skin tone\n1F9D1 1F3FC 200D 1F9 - 1D 200D 1F9D1 1F3FE \; fully-qualified # 🧑🏼‍🤝‍🧑🏾 - E12.1 people holding hands: medium-light skin tone\, medium-dark skin ton - e\n1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FF \; fully-qualified # - 🧑🏼‍🤝‍🧑🏿 E12.1 people holding hands: medium-light skin t - one\, dark skin tone\n1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FB \; fully- - qualified # 🧑🏽‍🤝‍🧑🏻 E12.1 people holding hands: med - ium skin tone\, light skin tone\n1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FC - \; fully-qualified # 🧑🏽‍🤝‍🧑🏼 E12.1 people holding - hands: medium skin tone\, medium-light skin tone\n1F9D1 1F3FD 200D 1F91D - 200D 1F9D1 1F3FD \; fully-qualified # 🧑🏽‍🤝‍🧑🏽 E1 - 2.1 people holding hands: medium skin tone\n1F9D1 1F3FD 200D 1F91D 200D 1F - 9D1 1F3FE \; fully-qualified # 🧑🏽‍🤝‍🧑🏾 E12.1 peo - ple holding hands: medium skin tone\, medium-dark skin tone\n1F9D1 1F3FD 2 - 00D 1F91D 200D 1F9D1 1F3FF \; fully-qualified # 🧑🏽‍🤝‍ - 🧑🏿 E12.1 people holding hands: medium skin tone\, dark skin tone\n1F - 9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FB \; fully-qualified # 🧑🏾 - ‍🤝‍🧑🏻 E12.1 people holding hands: medium-dark skin tone\, lig - ht skin tone\n1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FC \; fully-qualifie - d # 🧑🏾‍🤝‍🧑🏼 E12.1 people holding hands: medium-dark - skin tone\, medium-light skin tone\n1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3 - FD \; fully-qualified # 🧑🏾‍🤝‍🧑🏽 E12.1 people hol - ding hands: medium-dark skin tone\, medium skin tone\n1F9D1 1F3FE 200D 1F9 - 1D 200D 1F9D1 1F3FE \; fully-qualified # 🧑🏾‍🤝‍🧑🏾 - E12.1 people holding hands: medium-dark skin tone\n1F9D1 1F3FE 200D 1F91D - 200D 1F9D1 1F3FF \; fully-qualified # 🧑��‍🤝‍🧑🏿 - E12.1 people holding hands: medium-dark skin tone\, dark skin tone\n1F9D1 - 1F3FF 200D 1F91D 200D 1F9D1 1F3FB \; fully-qualified # 🧑🏿‍ - 🤝‍🧑🏻 E12.1 people holding hands: dark skin tone\, light skin to - ne\n1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FC \; fully-qualified # - 🧑🏿‍🤝‍🧑�� E12.1 people holding hands: dark skin tone\, - medium-light skin tone\n1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FD \; full - y-qualified # 🧑🏿‍🤝‍🧑�� E12.1 people holding hands: - dark skin tone\, medium skin tone\n1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3F - E \; fully-qualified # 🧑🏿‍🤝‍🧑🏾 E12.1 people hold - ing hands: dark skin tone\, medium-dark skin tone\n1F9D1 1F3FF 200D 1F91D - 200D 1F9D1 1F3FF \; fully-qualified # 🧑🏿‍🤝‍🧑🏿 E1 - 2.1 people holding hands: dark skin tone\n1F46D - \; fully-qualified # 👭 E2.0 women holding hands\n1F46D 1 - F3FB \; fully-qualified # 👭🏻 E12. - 1 women holding hands: light skin tone\n1F469 1F3FB 200D 1F91D 200D 1F469 - 1F3FC \; fully-qualified # 👩🏻‍🤝‍👩🏼 E12.1 women h - olding hands: light skin tone\, medium-light skin tone\n1F469 1F3FB 200D 1 - F91D 200D 1F469 1F3FD \; fully-qualified # 👩🏻‍🤝‍👩 - 🏽 E12.1 women holding hands: light skin tone\, medium skin tone\n1F469 - 1F3FB 200D 1F91D 200D 1F469 1F3FE \; fully-qualified # 👩🏻‍ - 🤝‍👩🏾 E12.1 women holding hands: light skin tone\, medium-dark s - kin tone\n1F469 1F3FB 200D 1F91D 200D 1F469 1F3FF \; fully-qualified - # 👩🏻‍🤝‍👩🏿 E12.1 women holding hands: light skin tone\ - , dark skin tone\n1F469 1F3FC 200D 1F91D 200D 1F469 1F3FB \; fully-qual - ified # 👩🏼‍🤝‍👩🏻 E12.1 women holding hands: medium-l - ight skin tone\, light skin tone\n1F46D 1F3FC - \; fully-qualified # 👭🏼 E12.1 women holding hands: medium-lig - ht skin tone\n1F469 1F3FC 200D 1F91D 200D 1F469 1F3FD \; fully-qualifie - d # 👩🏼‍🤝‍👩🏽 E12.1 women holding hands: medium-light - skin tone\, medium skin tone\n1F469 1F3FC 200D 1F91D 200D 1F469 1F3FE - \; fully-qualified # 👩🏼‍🤝‍👩🏾 E12.1 women holding ha - nds: medium-light skin tone\, medium-dark skin tone\n1F469 1F3FC 200D 1F91 - D 200D 1F469 1F3FF \; fully-qualified # 👩🏼‍🤝‍👩🏿 - E12.1 women holding hands: medium-light skin tone\, dark skin tone\n1F469 - 1F3FD 200D 1F91D 200D 1F469 1F3FB \; fully-qualified # 👩🏽‍ - 🤝‍👩🏻 E12.1 women holding hands: medium skin tone\, light skin t - one\n1F469 1F3FD 200D 1F91D 200D 1F469 1F3FC \; fully-qualified # - 👩🏽‍🤝‍👩🏼 E12.1 women holding hands: medium skin tone\, m - edium-light skin tone\n1F46D 1F3FD \; fully - -qualified # 👭🏽 E12.1 women holding hands: medium skin tone\n1F4 - 69 1F3FD 200D 1F91D 200D 1F469 1F3FE \; fully-qualified # 👩🏽 - ‍🤝‍👩🏾 E12.1 women holding hands: medium skin tone\, medium-da - rk skin tone\n1F469 1F3FD 200D 1F91D 200D 1F469 1F3FF \; fully-qualifie - d # 👩🏽‍🤝‍👩🏿 E12.1 women holding hands: medium skin - tone\, dark skin tone\n1F469 1F3FE 200D 1F91D 200D 1F469 1F3FB \; fully - -qualified # 👩🏾‍🤝‍👩🏻 E12.1 women holding hands: med - ium-dark skin tone\, light skin tone\n1F469 1F3FE 200D 1F91D 200D 1F469 1F - 3FC \; fully-qualified # 👩🏾‍🤝‍👩🏼 E12.1 women hol - ding hands: medium-dark skin tone\, medium-light skin tone\n1F469 1F3FE 20 - 0D 1F91D 200D 1F469 1F3FD \; fully-qualified # 👩🏾‍🤝‍ - 👩🏽 E12.1 women holding hands: medium-dark skin tone\, medium skin to - ne\n1F46D 1F3FE \; fully-qualified # - 👭🏾 E12.1 women holding hands: medium-dark skin tone\n1F469 1F3FE 200 - D 1F91D 200D 1F469 1F3FF \; fully-qualified # 👩🏾‍🤝‍ - 👩🏿 E12.1 women holding hands: medium-dark skin tone\, dark skin tone - \n1F469 1F3FF 200D 1F91D 200D 1F469 1F3FB \; fully-qualified # 👩 - 🏿‍🤝‍👩🏻 E12.1 women holding hands: dark skin tone\, light s - kin tone\n1F469 1F3FF 200D 1F91D 200D 1F469 1F3FC \; fully-qualified - # 👩🏿‍🤝‍👩🏼 E12.1 women holding hands: dark skin tone\, - medium-light skin tone\n1F469 1F3FF 200D 1F91D 200D 1F469 1F3FD \; ful - ly-qualified # 👩🏿‍🤝‍👩🏽 E12.1 women holding hands: d - ark skin tone\, medium skin tone\n1F469 1F3FF 200D 1F91D 200D 1F469 1F3FE - \; fully-qualified # 👩🏿‍🤝‍👩🏾 E12.1 women holding - hands: dark skin tone\, medium-dark skin tone\n1F46D 1F3FF - \; fully-qualified # 👭🏿 E12.1 women holding han - ds: dark skin tone\n1F46B \; fully-qu - alified # 👫 E2.0 woman and man holding hands\n1F46B 1F3FB - \; fully-qualified # 👫🏻 E12.1 woman and ma - n holding hands: light skin tone\n1F469 1F3FB 200D 1F91D 200D 1F468 1F3FC - \; fully-qualified # 👩🏻‍🤝‍👨🏼 E12.1 woman and man - holding hands: light skin tone\, medium-light skin tone\n1F469 1F3FB 200D - 1F91D 200D 1F468 1F3FD \; fully-qualified # 👩🏻‍🤝‍👨 - 🏽 E12.1 woman and man holding hands: light skin tone\, medium skin tone - \n1F469 1F3FB 200D 1F91D 200D 1F468 1F3FE \; fully-qualified # 👩 - 🏻‍🤝‍👨🏾 E12.1 woman and man holding hands: light skin tone\ - , medium-dark skin tone\n1F469 1F3FB 200D 1F91D 200D 1F468 1F3FF \; ful - ly-qualified # 👩🏻‍🤝‍👨🏿 E12.1 woman and man holding - hands: light skin tone\, dark skin tone\n1F469 1F3FC 200D 1F91D 200D 1F468 - 1F3FB \; fully-qualified # 👩🏼‍🤝‍👨🏻 E12.1 woman - and man holding hands: medium-light skin tone\, light skin tone\n1F46B 1F3 - FC \; fully-qualified # 👫🏼 E12.1 - woman and man holding hands: medium-light skin tone\n1F469 1F3FC 200D 1F91 - D 200D 1F468 1F3FD \; fully-qualified # 👩🏼‍🤝‍👨🏽 - E12.1 woman and man holding hands: medium-light skin tone\, medium skin to - ne\n1F469 1F3FC 200D 1F91D 200D 1F468 1F3FE \; fully-qualified # - 👩🏼‍🤝‍👨🏾 E12.1 woman and man holding hands: medium-light - skin tone\, medium-dark skin tone\n1F469 1F3FC 200D 1F91D 200D 1F468 1F3F - F \; fully-qualified # 👩🏼‍🤝‍👨🏿 E12.1 woman and m - an holding hands: medium-light skin tone\, dark skin tone\n1F469 1F3FD 200 - D 1F91D 200D 1F468 1F3FB \; fully-qualified # 👩🏽‍🤝‍ - 👨🏻 E12.1 woman and man holding hands: medium skin tone\, light skin - tone\n1F469 1F3FD 200D 1F91D 200D 1F468 1F3FC \; fully-qualified # - 👩🏽‍🤝‍👨🏼 E12.1 woman and man holding hands: medium skin - tone\, medium-light skin tone\n1F46B 1F3FD - \; fully-qualified # 👫🏽 E12.1 woman and man holding hands: mediu - m skin tone\n1F469 1F3FD 200D 1F91D 200D 1F468 1F3FE \; fully-qualified - # 👩🏽‍🤝‍👨🏾 E12.1 woman and man holding hands: mediu - m skin tone\, medium-dark skin tone\n1F469 1F3FD 200D 1F91D 200D 1F468 1F3 - FF \; fully-qualified # 👩🏽‍🤝‍👨🏿 E12.1 woman and - man holding hands: medium skin tone\, dark skin tone\n1F469 1F3FE 200D 1F9 - 1D 200D 1F468 1F3FB \; fully-qualified # 👩🏾‍🤝‍👨🏻 - E12.1 woman and man holding hands: medium-dark skin tone\, light skin ton - e\n1F469 1F3FE 200D 1F91D 200D 1F468 1F3FC \; fully-qualified # - 👩🏾‍🤝‍👨🏼 E12.1 woman and man holding hands: medium-dark - skin tone\, medium-light skin tone\n1F469 1F3FE 200D 1F91D 200D 1F468 1F3F - D \; fully-qualified # 👩🏾‍🤝‍👨🏽 E12.1 woman and m - an holding hands: medium-dark skin tone\, medium skin tone\n1F46B 1F3FE - \; fully-qualified # 👫🏾 E12.1 woman - and man holding hands: medium-dark skin tone\n1F469 1F3FE 200D 1F91D 200D - 1F468 1F3FF \; fully-qualified # 👩🏾‍🤝‍👨�� E12. - 1 woman and man holding hands: medium-dark skin tone\, dark skin tone\n1F4 - 69 1F3FF 200D 1F91D 200D 1F468 1F3FB \; fully-qualified # 👩🏿 - ‍🤝‍👨🏻 E12.1 woman and man holding hands: dark skin tone\, lig - ht skin tone\n1F469 1F3FF 200D 1F91D 200D 1F468 1F3FC \; fully-qualifie - d # 👩🏿‍🤝‍👨🏼 E12.1 woman and man holding hands: dark - skin tone\, medium-light skin tone\n1F469 1F3FF 200D 1F91D 200D 1F468 1F3 - FD \; fully-qualified # 👩🏿‍🤝‍👨🏽 E12.1 woman and - man holding hands: dark skin tone\, medium skin tone\n1F469 1F3FF 200D 1F9 - 1D 200D 1F468 1F3FE \; fully-qualified # 👩🏿‍🤝‍👨🏾 - E12.1 woman and man holding hands: dark skin tone\, medium-dark skin tone - \n1F46B 1F3FF \; fully-qualified # 👫 - 🏿 E12.1 woman and man holding hands: dark skin tone\n1F46C - \; fully-qualified # 👬 E2.0 men holding ha - nds\n1F46C 1F3FB \; fully-qualified # - 👬🏻 E12.1 men holding hands: light skin tone\n1F468 1F3FB 200D 1F91D - 200D 1F468 1F3FC \; fully-qualified # 👨🏻‍🤝‍👨🏼 E1 - 2.1 men holding hands: light skin tone\, medium-light skin tone\n1F468 1F3 - FB 200D 1F91D 200D 1F468 1F3FD \; fully-qualified # 👨🏻‍🤝 - ‍👨🏽 E12.1 men holding hands: light skin tone\, medium skin tone\n1 - F468 1F3FB 200D 1F91D 200D 1F468 1F3FE \; fully-qualified # 👨 - 🏻‍🤝‍👨🏾 E12.1 men holding hands: light skin tone\, medium-d - ark skin tone\n1F468 1F3FB 200D 1F91D 200D 1F468 1F3FF \; fully-qualifi - ed # 👨🏻‍🤝‍👨🏿 E12.1 men holding hands: light skin to - ne\, dark skin tone\n1F468 1F3FC 200D 1F91D 200D 1F468 1F3FB \; fully-q - ualified # 👨🏼‍🤝‍👨🏻 E12.1 men holding hands: medium- - light skin tone\, light skin tone\n1F46C 1F3FC - \; fully-qualified # 👬🏼 E12.1 men holding hands: medium-ligh - t skin tone\n1F468 1F3FC 200D 1F91D 200D 1F468 1F3FD \; fully-qualified - # 👨🏼‍🤝‍👨🏽 E12.1 men holding hands: medium-light sk - in tone\, medium skin tone\n1F468 1F3FC 200D 1F91D 200D 1F468 1F3FE \; - fully-qualified # 👨🏼‍🤝‍👨�� E12.1 men holding hands - : medium-light skin tone\, medium-dark skin tone\n1F468 1F3FC 200D 1F91D 2 - 00D 1F468 1F3FF \; fully-qualified # 👨🏼‍🤝‍👨🏿 E12 - .1 men holding hands: medium-light skin tone\, dark skin tone\n1F468 1F3FD - 200D 1F91D 200D 1F468 1F3FB \; fully-qualified # 👨🏽‍🤝 - ‍👨🏻 E12.1 men holding hands: medium skin tone\, light skin tone\n1 - F468 1F3FD 200D 1F91D 200D 1F468 1F3FC \; fully-qualified # 👨 - 🏽‍🤝‍👨🏼 E12.1 men holding hands: medium skin tone\, medium- - light skin tone\n1F46C 1F3FD \; fully-quali - fied # 👬🏽 E12.1 men holding hands: medium skin tone\n1F468 1F3FD - 200D 1F91D 200D 1F468 1F3FE \; fully-qualified # 👨🏽‍🤝 - ‍👨🏾 E12.1 men holding hands: medium skin tone\, medium-dark skin t - one\n1F468 1F3FD 200D 1F91D 200D 1F468 1F3FF \; fully-qualified # - 👨🏽‍🤝‍👨🏿 E12.1 men holding hands: medium skin tone\, dar - k skin tone\n1F468 1F3FE 200D 1F91D 200D 1F468 1F3FB \; fully-qualified - # 👨🏾‍🤝‍👨🏻 E12.1 men holding hands: medium-dark ski - n tone\, light skin tone\n1F468 1F3FE 200D 1F91D 200D 1F468 1F3FC \; fu - lly-qualified # 👨🏾‍🤝‍👨🏼 E12.1 men holding hands: me - dium-dark skin tone\, medium-light skin tone\n1F468 1F3FE 200D 1F91D 200D - 1F468 1F3FD \; fully-qualified # 👨🏾‍🤝‍👨🏽 E12.1 m - en holding hands: medium-dark skin tone\, medium skin tone\n1F46C 1F3FE - \; fully-qualified # 👬🏾 E12.1 men h - olding hands: medium-dark skin tone\n1F468 1F3FE 200D 1F91D 200D 1F468 1F3 - FF \; fully-qualified # 👨🏾‍🤝‍👨🏿 E12.1 men holdin - g hands: medium-dark skin tone\, dark skin tone\n1F468 1F3FF 200D 1F91D 20 - 0D 1F468 1F3FB \; fully-qualified # 👨🏿‍🤝‍👨🏻 E12. - 1 men holding hands: dark skin tone\, light skin tone\n1F468 1F3FF 200D 1F - 91D 200D 1F468 1F3FC \; fully-qualified # 👨🏿‍🤝‍👨 - 🏼 E12.1 men holding hands: dark skin tone\, medium-light skin tone\n1F4 - 68 1F3FF 200D 1F91D 200D 1F468 1F3FD \; fully-qualified # 👨🏿 - ‍🤝‍👨🏽 E12.1 men holding hands: dark skin tone\, medium skin t - one\n1F468 1F3FF 200D 1F91D 200D 1F468 1F3FE \; fully-qualified # - 👨🏿‍🤝‍👨🏾 E12.1 men holding hands: dark skin tone\, mediu - m-dark skin tone\n1F46C 1F3FF \; fully-qual - ified # 👬🏿 E12.1 men holding hands: dark skin tone\n1F48F - \; fully-qualified # 💏 E2.0 kiss\n1F - 469 200D 2764 FE0F 200D 1F48B 200D 1F468 \; fully-qualified # 👩‍ - ❤️‍💋‍👨 E2.0 kiss: woman\, man\n1F469 200D 2764 200D 1F48B 20 - 0D 1F468 \; minimally-qualified # 👩‍❤‍💋‍👨 E2.0 kiss: - woman\, man\n1F468 200D 2764 FE0F 200D 1F48B 200D 1F468 \; fully-qualifie - d # 👨‍❤️‍💋‍👨 E2.0 kiss: man\, man\n1F468 200D 2764 - 200D 1F48B 200D 1F468 \; minimally-qualified # 👨‍❤‍💋‍ - 👨 E2.0 kiss: man\, man\n1F469 200D 2764 FE0F 200D 1F48B 200D 1F469 \; f - ully-qualified # 👩‍❤️‍💋‍👩 E2.0 kiss: woman\, woman\ - n1F469 200D 2764 200D 1F48B 200D 1F469 \; minimally-qualified # 👩 - ‍❤‍💋‍👩 E2.0 kiss: woman\, woman\n1F491 - \; fully-qualified # 💑 E2.0 couple with heart\n1F46 - 9 200D 2764 FE0F 200D 1F468 \; fully-qualified # 👩‍❤ - ️‍👨 E2.0 couple with heart: woman\, man\n1F469 200D 2764 200D 1F468 - \; minimally-qualified # 👩‍❤‍👨 E2.0 couple wi - th heart: woman\, man\n1F468 200D 2764 FE0F 200D 1F468 \; fully - -qualified # 👨‍❤️‍👨 E2.0 couple with heart: man\, man\n1 - F468 200D 2764 200D 1F468 \; minimally-qualified # 👨‍ - ❤‍👨 E2.0 couple with heart: man\, man\n1F469 200D 2764 FE0F 200D 1F - 469 \; fully-qualified # 👩‍❤️‍👩 E2.0 couple w - ith heart: woman\, woman\n1F469 200D 2764 200D 1F469 \; mi - nimally-qualified # 👩‍❤‍👩 E2.0 couple with heart: woman\, woma - n\n1F46A \; fully-qualified # - 👪 E2.0 family\n1F468 200D 1F469 200D 1F466 \; fully-qual - ified # 👨‍👩‍👦 E2.0 family: man\, woman\, boy\n1F468 200D - 1F469 200D 1F467 \; fully-qualified # 👨‍👩‍ - 👧 E2.0 family: man\, woman\, girl\n1F468 200D 1F469 200D 1F467 200D 1F4 - 66 \; fully-qualified # 👨‍👩‍👧‍👦 E2.0 family: man - \, woman\, girl\, boy\n1F468 200D 1F469 200D 1F466 200D 1F466 \; fully - -qualified # 👨‍👩‍👦‍👦 E2.0 family: man\, woman\, boy\ - , boy\n1F468 200D 1F469 200D 1F467 200D 1F467 \; fully-qualified # - 👨‍👩‍👧‍👧 E2.0 family: man\, woman\, girl\, girl\n1F468 2 - 00D 1F468 200D 1F466 \; fully-qualified # 👨‍👨 - ‍👦 E2.0 family: man\, man\, boy\n1F468 200D 1F468 200D 1F467 - \; fully-qualified # 👨‍👨‍👧 E2.0 family: man\, man\ - , girl\n1F468 200D 1F468 200D 1F467 200D 1F466 \; fully-qualified - # 👨‍👨‍👧‍👦 E2.0 family: man\, man\, girl\, boy\n1F468 200 - D 1F468 200D 1F466 200D 1F466 \; fully-qualified # 👨‍👨‍ - 👦‍👦 E2.0 family: man\, man\, boy\, boy\n1F468 200D 1F468 200D 1F46 - 7 200D 1F467 \; fully-qualified # 👨‍👨‍👧‍👧 E2.0 f - amily: man\, man\, girl\, girl\n1F469 200D 1F469 200D 1F466 - \; fully-qualified # 👩‍👩‍👦 E2.0 family: woman\, woman\, - boy\n1F469 200D 1F469 200D 1F467 \; fully-qualified # - 👩‍👩‍👧 E2.0 family: woman\, woman\, girl\n1F469 200D 1F469 200 - D 1F467 200D 1F466 \; fully-qualified # 👩‍👩‍��‍ - 👦 E2.0 family: woman\, woman\, girl\, boy\n1F469 200D 1F469 200D 1F466 - 200D 1F466 \; fully-qualified # 👩‍👩‍👦‍👦 E2.0 fam - ily: woman\, woman\, boy\, boy\n1F469 200D 1F469 200D 1F467 200D 1F467 - \; fully-qualified # 👩‍👩‍👧‍👧 E2.0 family: woman\, w - oman\, girl\, girl\n1F468 200D 1F466 \; fully-qu - alified # 👨‍👦 E4.0 family: man\, boy\n1F468 200D 1F466 200D 1F - 466 \; fully-qualified # 👨‍👦‍👦 E4.0 family - : man\, boy\, boy\n1F468 200D 1F467 \; fully-qua - lified # 👨‍👧 E4.0 family: man\, girl\n1F468 200D 1F467 200D 1F - 466 \; fully-qualified # 👨‍👧‍👦 E4.0 family - : man\, girl\, boy\n1F468 200D 1F467 200D 1F467 \; fully-qu - alified # 👨‍👧‍👧 E4.0 family: man\, girl\, girl\n1F469 200 - D 1F466 \; fully-qualified # 👩‍👦 E4. - 0 family: woman\, boy\n1F469 200D 1F466 200D 1F466 \; fully - -qualified # 👩‍👦‍👦 E4.0 family: woman\, boy\, boy\n1F469 - 200D 1F467 \; fully-qualified # 👩‍👧 - E4.0 family: woman\, girl\n1F469 200D 1F467 200D 1F466 \; f - ully-qualified # 👩‍👧‍👦 E4.0 family: woman\, girl\, boy\n1 - F469 200D 1F467 200D 1F467 \; fully-qualified # 👩‍ - 👧‍👧 E4.0 family: woman\, girl\, girl\n\n# subgroup: person-symbol\ - n1F5E3 FE0F \; fully-qualified # 🗣 - ️ E2.0 speaking head\n1F5E3 \; unqu - alified # 🗣 E2.0 speaking head\n1F464 - \; fully-qualified # 👤 E2.0 bust in silhouette\n1F465 - \; fully-qualified # 👥 E2.0 bus - ts in silhouette\n1F463 \; fully-qual - ified # 👣 E2.0 footprints\n\n# People & Body subtotal: 2398\n# Peo - ple & Body subtotal: 473 w/o modifiers\n\n# group: Component\n\n# subgrou - p: skin-tone\n1F3FB \; component - # 🏻 E2.0 light skin tone\n1F3FC - \; component # 🏼 E2.0 medium-light skin tone\n1F3FD - \; component # 🏽 E2.0 medium - skin tone\n1F3FE \; component - # 🏾 E2.0 medium-dark skin tone\n1F3FF - \; component # 🏿 E2.0 dark skin tone\n\n# subgroup: ha - ir-style\n1F9B0 \; component - # 🦰 E11.0 red hair\n1F9B1 \; com - ponent # 🦱 E11.0 curly hair\n1F9B3 - \; component # 🦳 E11.0 white hair\n1F9B2 - \; component # 🦲 E11.0 bald\n\n# - Component subtotal: 9\n# Component subtotal: 4 w/o modifiers\n\n# group: - Animals & Nature\n\n# subgroup: animal-mammal\n1F435 - \; fully-qualified # 🐵 E2.0 monkey face\n1F412 - \; fully-qualified # 🐒 E2.0 monke - y\n1F98D \; fully-qualified # - 🦍 E4.0 gorilla\n1F9A7 \; fully-qua - lified # 🦧 E12.1 orangutan\n1F436 - \; fully-qualified # 🐶 E2.0 dog face\n1F415 - \; fully-qualified # 🐕 E2.0 dog\n1F9AE - \; fully-qualified # 🦮 E12.1 guide dog\n - 1F415 200D 1F9BA \; fully-qualified # 🐕 - ‍🦺 E12.1 service dog\n1F429 \; f - ully-qualified # 🐩 E2.0 poodle\n1F43A - \; fully-qualified # 🐺 E2.0 wolf\n1F98A - \; fully-qualified # 🦊 E4.0 fox\n1F99D - \; fully-qualified # 🦝 E11.0 raccoon\n1F - 431 \; fully-qualified # 🐱 E2. - 0 cat face\n1F408 \; fully-qualified - # 🐈 E2.0 cat\n1F981 \; fully-q - ualified # 🦁 E2.0 lion\n1F42F - \; fully-qualified # 🐯 E2.0 tiger face\n1F405 - \; fully-qualified # 🐅 E2.0 tiger\n1F406 - \; fully-qualified # 🐆 E2.0 leopard\n1F4 - 34 \; fully-qualified # 🐴 E2.0 - horse face\n1F40E \; fully-qualified - # 🐎 E2.0 horse\n1F984 \; full - y-qualified # 🦄 E2.0 unicorn\n1F993 - \; fully-qualified # 🦓 E5.0 zebra\n1F98C - \; fully-qualified # 🦌 E4.0 deer\n1F42E - \; fully-qualified # 🐮 E2.0 cow face\n1F - 402 \; fully-qualified # 🐂 E2. - 0 ox\n1F403 \; fully-qualified # - 🐃 E2.0 water buffalo\n1F404 \; ful - ly-qualified # 🐄 E2.0 cow\n1F437 - \; fully-qualified # 🐷 E2.0 pig face\n1F416 - \; fully-qualified # 🐖 E2.0 pig\n1F417 - \; fully-qualified # 🐗 E2.0 boar\n1F43D - \; fully-qualified # 🐽 E2.0 pig - nose\n1F40F \; fully-qualified # - 🐏 E2.0 ram\n1F411 \; fully-qualif - ied # 🐑 E2.0 ewe\n1F410 \; ful - ly-qualified # 🐐 E2.0 goat\n1F42A - \; fully-qualified # 🐪 E2.0 camel\n1F42B - \; fully-qualified # 🐫 E2.0 two-hump camel\n1F999 - \; fully-qualified # 🦙 E11.0 lla - ma\n1F992 \; fully-qualified # - 🦒 E5.0 giraffe\n1F418 \; fully-qua - lified # 🐘 E2.0 elephant\n1F98F - \; fully-qualified # 🦏 E4.0 rhinoceros\n1F99B - \; fully-qualified # 🦛 E11.0 hippopotamus\n1F42D - \; fully-qualified # 🐭 E2.0 mo - use face\n1F401 \; fully-qualified - # 🐁 E2.0 mouse\n1F400 \; fully-q - ualified # 🐀 E2.0 rat\n1F439 \ - ; fully-qualified # 🐹 E2.0 hamster\n1F430 - \; fully-qualified # 🐰 E2.0 rabbit face\n1F407 - \; fully-qualified # 🐇 E2.0 rabbit\n1F - 43F FE0F \; fully-qualified # 🐿️ - E2.0 chipmunk\n1F43F \; unqualified - # 🐿 E2.0 chipmunk\n1F994 \; - fully-qualified # 🦔 E5.0 hedgehog\n1F987 - \; fully-qualified # 🦇 E4.0 bat\n1F43B - \; fully-qualified # 🐻 E2.0 bear\n1F428 - \; fully-qualified # 🐨 E2.0 koala\n1 - F43C \; fully-qualified # 🐼 E2 - .0 panda\n1F9A5 \; fully-qualified - # 🦥 E12.1 sloth\n1F9A6 \; fully- - qualified # 🦦 E12.1 otter\n1F9A8 - \; fully-qualified # 🦨 E12.1 skunk\n1F998 - \; fully-qualified # 🦘 E11.0 kangaroo\n1F9A1 - \; fully-qualified # 🦡 E11.0 badger\n - 1F43E \; fully-qualified # 🐾 E - 2.0 paw prints\n\n# subgroup: animal-bird\n1F983 - \; fully-qualified # 🦃 E2.0 turkey\n1F414 - \; fully-qualified # 🐔 E2.0 chicken\n1F413 - \; fully-qualified # 🐓 E2.0 ro - oster\n1F423 \; fully-qualified # - 🐣 E2.0 hatching chick\n1F424 \; f - ully-qualified # 🐤 E2.0 baby chick\n1F425 - \; fully-qualified # 🐥 E2.0 front-facing baby chick\n1F - 426 \; fully-qualified # 🐦 E2. - 0 bird\n1F427 \; fully-qualified - # 🐧 E2.0 penguin\n1F54A FE0F \; fully-q - ualified # 🕊️ E2.0 dove\n1F54A - \; unqualified # 🕊 E2.0 dove\n1F985 - \; fully-qualified # 🦅 E4.0 eagle\n1F986 - \; fully-qualified # 🦆 E4.0 duck\n1F9A2 - \; fully-qualified # 🦢 E11.0 swan - \n1F989 \; fully-qualified # 🦉 - E4.0 owl\n1F9A9 \; fully-qualified - # 🦩 E12.1 flamingo\n1F99A \; fu - lly-qualified # 🦚 E11.0 peacock\n1F99C - \; fully-qualified # 🦜 E11.0 parrot\n\n# subgroup: animal- - amphibian\n1F438 \; fully-qualified - # 🐸 E2.0 frog\n\n# subgroup: animal-reptile\n1F40A - \; fully-qualified # 🐊 E2.0 crocodile\n1F422 - \; fully-qualified # 🐢 E2.0 turt - le\n1F98E \; fully-qualified # - 🦎 E4.0 lizard\n1F40D \; fully-qual - ified # 🐍 E2.0 snake\n1F432 \; - fully-qualified # 🐲 E2.0 dragon face\n1F409 - \; fully-qualified # 🐉 E2.0 dragon\n1F995 - \; fully-qualified # 🦕 E5.0 sauropod\n1F - 996 \; fully-qualified # 🦖 E5. - 0 T-Rex\n\n# subgroup: animal-marine\n1F433 - \; fully-qualified # 🐳 E2.0 spouting whale\n1F40B - \; fully-qualified # 🐋 E2.0 whale\n1F42C - \; fully-qualified # 🐬 E2.0 d - olphin\n1F41F \; fully-qualified - # 🐟 E2.0 fish\n1F420 \; fully-qual - ified # 🐠 E2.0 tropical fish\n1F421 - \; fully-qualified # 🐡 E2.0 blowfish\n1F988 - \; fully-qualified # �� E4.0 shark\n1F419 - \; fully-qualified # 🐙 E2.0 octopu - s\n1F41A \; fully-qualified # - 🐚 E2.0 spiral shell\n\n# subgroup: animal-bug\n1F40C - \; fully-qualified # 🐌 E2.0 snail\n1F98B - \; fully-qualified # 🦋 E4.0 butterfly - \n1F41B \; fully-qualified # 🐛 - E2.0 bug\n1F41C \; fully-qualified - # 🐜 E2.0 ant\n1F41D \; fully-qu - alified # 🐝 E2.0 honeybee\n1F41E - \; fully-qualified # 🐞 E2.0 lady beetle\n1F997 - \; fully-qualified # 🦗 E5.0 cricket\n1F577 FE0F - \; fully-qualified # 🕷️ E2.0 spi - der\n1F577 \; unqualified # - 🕷 E2.0 spider\n1F578 FE0F \; fully-qual - ified # 🕸️ E2.0 spider web\n1F578 - \; unqualified # 🕸 E2.0 spider web\n1F982 - \; fully-qualified # 🦂 E2.0 scorpion\n1F99F - \; fully-qualified # 🦟 E11.0 mo - squito\n1F9A0 \; fully-qualified - # 🦠 E11.0 microbe\n\n# subgroup: plant-flower\n1F490 - \; fully-qualified # 💐 E2.0 bouquet\n1F338 - \; fully-qualified # 🌸 E2.0 cherry - blossom\n1F4AE \; fully-qualified - # 💮 E2.0 white flower\n1F3F5 FE0F \; f - ully-qualified # 🏵️ E2.0 rosette\n1F3F5 - \; unqualified # 🏵 E2.0 rosette\n1F339 - \; fully-qualified # 🌹 E2.0 rose\n1F940 - \; fully-qualified # 🥀 E4.0 wilt - ed flower\n1F33A \; fully-qualified - # 🌺 E2.0 hibiscus\n1F33B \; ful - ly-qualified # 🌻 E2.0 sunflower\n1F33C - \; fully-qualified # 🌼 E2.0 blossom\n1F337 - \; fully-qualified # 🌷 E2.0 tulip\n\n# subgro - up: plant-other\n1F331 \; fully-quali - fied # 🌱 E2.0 seedling\n1F332 - \; fully-qualified # 🌲 E2.0 evergreen tree\n1F333 - \; fully-qualified # 🌳 E2.0 deciduous tree\n1F3 - 34 \; fully-qualified # 🌴 E2.0 - palm tree\n1F335 \; fully-qualified - # 🌵 E2.0 cactus\n1F33E \; full - y-qualified # 🌾 E2.0 sheaf of rice\n1F33F - \; fully-qualified # 🌿 E2.0 herb\n2618 FE0F - \; fully-qualified # ☘️ E2.0 shamrock\n2618 - \; unqualified # ☘ E2.0 sh - amrock\n1F340 \; fully-qualified - # 🍀 E2.0 four leaf clover\n1F341 \ - ; fully-qualified # 🍁 E2.0 maple leaf\n1F342 - \; fully-qualified # 🍂 E2.0 fallen leaf\n1F343 - \; fully-qualified # 🍃 E2.0 leaf fl - uttering in wind\n\n# Animals & Nature subtotal: 133\n# Animals & Nature - subtotal: 133 w/o modifiers\n\n# group: Food & Drink\n\n# subgroup: food- - fruit\n1F347 \; fully-qualified # - 🍇 E2.0 grapes\n1F348 \; fully-qua - lified # 🍈 E2.0 melon\n1F349 \ - ; fully-qualified # 🍉 E2.0 watermelon\n1F34A - \; fully-qualified # 🍊 E2.0 tangerine\n1F34B - \; fully-qualified # 🍋 E2.0 lemon\n1F - 34C \; fully-qualified # 🍌 E2. - 0 banana\n1F34D \; fully-qualified - # 🍍 E2.0 pineapple\n1F96D \; ful - ly-qualified # 🥭 E11.0 mango\n1F34E - \; fully-qualified # 🍎 E2.0 red apple\n1F34F - \; fully-qualified # 🍏 E2.0 green apple\n1F350 - \; fully-qualified # 🍐 E2.0 p - ear\n1F351 \; fully-qualified # - 🍑 E2.0 peach\n1F352 \; fully-quali - fied # 🍒 E2.0 cherries\n1F353 - \; fully-qualified # 🍓 E2.0 strawberry\n1F95D - \; fully-qualified # 🥝 E4.0 kiwi fruit\n1F345 - \; fully-qualified # 🍅 E2.0 tomato\ - n1F965 \; fully-qualified # 🥥 - E5.0 coconut\n\n# subgroup: food-vegetable\n1F951 - \; fully-qualified # 🥑 E4.0 avocado\n1F346 - \; fully-qualified # 🍆 E2.0 eggplant\n1F9 - 54 \; fully-qualified # 🥔 E4.0 - potato\n1F955 \; fully-qualified - # 🥕 E4.0 carrot\n1F33D \; fully-q - ualified # 🌽 E2.0 ear of corn\n1F336 FE0F - \; fully-qualified # 🌶️ E2.0 hot pepper\n1F336 - \; unqualified # 🌶 E2.0 hot pepper\n1 - F952 \; fully-qualified # 🥒 E4 - .0 cucumber\n1F96C \; fully-qualified - # 🥬 E11.0 leafy green\n1F966 - \; fully-qualified # 🥦 E5.0 broccoli\n1F9C4 - \; fully-qualified # 🧄 E12.1 garlic\n1F9C5 - \; fully-qualified # 🧅 E12.1 onion\n1F34 - 4 \; fully-qualified # �� E2. - 0 mushroom\n1F95C \; fully-qualified - # 🥜 E4.0 peanuts\n1F330 \; ful - ly-qualified # 🌰 E2.0 chestnut\n\n# subgroup: food-prepared\n1F35E - \; fully-qualified # 🍞 E2.0 br - ead\n1F950 \; fully-qualified # - 🥐 E4.0 croissant\n1F956 \; fully-q - ualified # 🥖 E4.0 baguette bread\n1F968 - \; fully-qualified # 🥨 E5.0 pretzel\n1F96F - \; fully-qualified # 🥯 E11.0 bagel\n1F95E - \; fully-qualified # 🥞 E4.0 panc - akes\n1F9C7 \; fully-qualified # - 🧇 E12.1 waffle\n1F9C0 \; fully-qua - lified # 🧀 E2.0 cheese wedge\n1F356 - \; fully-qualified # 🍖 E2.0 meat on bone\n1F357 - \; fully-qualified # 🍗 E2.0 poultry leg\n1F - 969 \; fully-qualified # 🥩 E5. - 0 cut of meat\n1F953 \; fully-qualifi - ed # 🥓 E4.0 bacon\n1F354 \; fu - lly-qualified # 🍔 E2.0 hamburger\n1F35F - \; fully-qualified # 🍟 E2.0 french fries\n1F355 - \; fully-qualified # 🍕 E2.0 pizza\n1F32 - D \; fully-qualified # 🌭 E2.0 - hot dog\n1F96A \; fully-qualified - # 🥪 E5.0 sandwich\n1F32E \; fully - -qualified # 🌮 E2.0 taco\n1F32F - \; fully-qualified # 🌯 E2.0 burrito\n1F959 - \; fully-qualified # 🥙 E4.0 stuffed flatbread\n1F9C6 - \; fully-qualified # 🧆 E12.1 - falafel\n1F95A \; fully-qualified - # 🥚 E4.0 egg\n1F373 \; fully-qual - ified # 🍳 E2.0 cooking\n1F958 - \; fully-qualified # 🥘 E4.0 shallow pan of food\n1F372 - \; fully-qualified # 🍲 E2.0 pot of food\n1 - F963 \; fully-qualified # 🥣 E5 - .0 bowl with spoon\n1F957 \; fully-qu - alified # 🥗 E4.0 green salad\n1F37F - \; fully-qualified # 🍿 E2.0 popcorn\n1F9C8 - \; fully-qualified # 🧈 E12.1 butter\n1F9C2 - \; fully-qualified # 🧂 E11.0 salt\n - 1F96B \; fully-qualified # 🥫 E - 5.0 canned food\n\n# subgroup: food-asian\n1F371 - \; fully-qualified # 🍱 E2.0 bento box\n1F358 - \; fully-qualified # 🍘 E2.0 rice cracker - \n1F359 \; fully-qualified # 🍙 - E2.0 rice ball\n1F35A \; fully-quali - fied # 🍚 E2.0 cooked rice\n1F35B - \; fully-qualified # 🍛 E2.0 curry rice\n1F35C - \; fully-qualified # 🍜 E2.0 steaming bowl\n1F35D - \; fully-qualified # 🍝 E2.0 s - paghetti\n1F360 \; fully-qualified - # 🍠 E2.0 roasted sweet potato\n1F362 - \; fully-qualified # 🍢 E2.0 oden\n1F363 - \; fully-qualified # 🍣 E2.0 sushi\n1F364 - \; fully-qualified # 🍤 E2.0 fried shrimp\ - n1F365 \; fully-qualified # 🍥 - E2.0 fish cake with swirl\n1F96E \; f - ully-qualified # 🥮 E11.0 moon cake\n1F361 - \; fully-qualified # 🍡 E2.0 dango\n1F95F - \; fully-qualified # 🥟 E5.0 dumpling\n1F960 - \; fully-qualified # 🥠 E5.0 fo - rtune cookie\n1F961 \; fully-qualifie - d # 🥡 E5.0 takeout box\n\n# subgroup: food-marine\n1F980 - \; fully-qualified # 🦀 E2.0 crab\n1F99E - \; fully-qualified # 🦞 E11.0 l - obster\n1F990 \; fully-qualified - # 🦐 E4.0 shrimp\n1F991 \; fully-qu - alified # 🦑 E4.0 squid\n1F9AA - \; fully-qualified # 🦪 E12.1 oyster\n\n# subgroup: food-sweet\n1F36 - 6 \; fully-qualified # 🍦 E2.0 - soft ice cream\n1F367 \; fully-qualif - ied # 🍧 E2.0 shaved ice\n1F368 - \; fully-qualified # 🍨 E2.0 ice cream\n1F369 - \; fully-qualified # 🍩 E2.0 doughnut\n1F36A - \; fully-qualified # 🍪 E2.0 cookie\n1 - F382 \; fully-qualified # 🎂 E2 - .0 birthday cake\n1F370 \; fully-qual - ified # 🍰 E2.0 shortcake\n1F9C1 - \; fully-qualified # 🧁 E11.0 cupcake\n1F967 - \; fully-qualified # 🥧 E5.0 pie\n1F36B - \; fully-qualified # 🍫 E2.0 chocolate bar\ - n1F36C \; fully-qualified # 🍬 - E2.0 candy\n1F36D \; fully-qualified - # 🍭 E2.0 lollipop\n1F36E \; fu - lly-qualified # 🍮 E2.0 custard\n1F36F - \; fully-qualified # 🍯 E2.0 honey pot\n\n# subgroup: drink\ - n1F37C \; fully-qualified # 🍼 - E2.0 baby bottle\n1F95B \; fully-qual - ified # 🥛 E4.0 glass of milk\n2615 - \; fully-qualified # ☕ E2.0 hot beverage\n1F375 - \; fully-qualified # 🍵 E2.0 teacup without h - andle\n1F376 \; fully-qualified # - 🍶 E2.0 sake\n1F37E \; fully-quali - fied # 🍾 E2.0 bottle with popping cork\n1F377 - \; fully-qualified # 🍷 E2.0 wine glass\n1F378 - \; fully-qualified # 🍸 E2.0 cocktai - l glass\n1F379 \; fully-qualified - # 🍹 E2.0 tropical drink\n1F37A \; - fully-qualified # �� E2.0 beer mug\n1F37B - \; fully-qualified # 🍻 E2.0 clinking beer mugs\n1F942 - \; fully-qualified # 🥂 E4.0 c - linking glasses\n1F943 \; fully-quali - fied # 🥃 E4.0 tumbler glass\n1F964 - \; fully-qualified # 🥤 E5.0 cup with straw\n1F9C3 - \; fully-qualified # 🧃 E12.1 beverage box\ - n1F9C9 \; fully-qualified # 🧉 - E12.1 mate\n1F9CA \; fully-qualified - # 🧊 E12.1 ice\n\n# subgroup: dishware\n1F962 - \; fully-qualified # 🥢 E5.0 chopsticks\n1F37D FE0F - \; fully-qualified # 🍽️ E2.0 fork - and knife with plate\n1F37D \; unqual - ified # 🍽 E2.0 fork and knife with plate\n1F374 - \; fully-qualified # 🍴 E2.0 fork and knife\n1 - F944 \; fully-qualified # 🥄 E4 - .0 spoon\n1F52A \; fully-qualified - # 🔪 E2.0 kitchen knife\n1F3FA \; - fully-qualified # 🏺 E2.0 amphora\n\n# Food & Drink subtotal: 123\ - n# Food & Drink subtotal: 123 w/o modifiers\n\n# group: Travel & Places\n - \n# subgroup: place-map\n1F30D \; ful - ly-qualified # 🌍 E2.0 globe showing Europe-Africa\n1F30E - \; fully-qualified # 🌎 E2.0 globe showin - g Americas\n1F30F \; fully-qualified - # 🌏 E2.0 globe showing Asia-Australia\n1F310 - \; fully-qualified # 🌐 E2.0 globe with meridians\n1F - 5FA FE0F \; fully-qualified # 🗺️ - E2.0 world map\n1F5FA \; unqualified - # 🗺 E2.0 world map\n1F5FE - \; fully-qualified # �� E2.0 map of Japan\n1F9ED - \; fully-qualified # 🧭 E11.0 compass\n\n# subgr - oup: place-geographic\n1F3D4 FE0F \; fully - -qualified # 🏔️ E2.0 snow-capped mountain\n1F3D4 - \; unqualified # 🏔 E2.0 snow-capped mounta - in\n26F0 FE0F \; fully-qualified # - ⛰️ E2.0 mountain\n26F0 \; unqual - ified # ⛰ E2.0 mountain\n1F30B - \; fully-qualified # 🌋 E2.0 volcano\n1F5FB - \; fully-qualified # 🗻 E2.0 mount fuji\n1F3D5 FE0F - \; fully-qualified # 🏕️ E2.0 cam - ping\n1F3D5 \; unqualified # - 🏕 E2.0 camping\n1F3D6 FE0F \; fully-qua - lified # 🏖️ E2.0 beach with umbrella\n1F3D6 - \; unqualified # 🏖 E2.0 beach with umbrella\n1F - 3DC FE0F \; fully-qualified # 🏜️ - E2.0 desert\n1F3DC \; unqualified - # �� E2.0 desert\n1F3DD FE0F \; f - ully-qualified # 🏝️ E2.0 desert island\n1F3DD - \; unqualified # 🏝 E2.0 desert island\n1F3DE - FE0F \; fully-qualified # 🏞️ E2.0 - national park\n1F3DE \; unqualified - # 🏞 E2.0 national park\n\n# subgroup: place-building\n1F3DF FE0 - F \; fully-qualified # 🏟️ E2.0 st - adium\n1F3DF \; unqualified # - 🏟 E2.0 stadium\n1F3DB FE0F \; fully-qu - alified # 🏛️ E2.0 classical building\n1F3DB - \; unqualified # 🏛 E2.0 classical building\n1F3 - D7 FE0F \; fully-qualified # 🏗️ E - 2.0 building construction\n1F3D7 \; u - nqualified # 🏗 E2.0 building construction\n1F9F1 - \; fully-qualified # 🧱 E11.0 brick\n1F3D8 FE - 0F \; fully-qualified # 🏘️ E2.0 h - ouses\n1F3D8 \; unqualified # - 🏘 E2.0 houses\n1F3DA FE0F \; fully-qua - lified # 🏚️ E2.0 derelict house\n1F3DA - \; unqualified # 🏚 E2.0 derelict house\n1F3E0 - \; fully-qualified # 🏠 E2.0 house\n1 - F3E1 \; fully-qualified # 🏡 E2 - .0 house with garden\n1F3E2 \; fully- - qualified # 🏢 E2.0 office building\n1F3E3 - \; fully-qualified # 🏣 E2.0 Japanese post office\n1F3E4 - \; fully-qualified # 🏤 E2.0 p - ost office\n1F3E5 \; fully-qualified - # 🏥 E2.0 hospital\n1F3E6 \; fu - lly-qualified # �� E2.0 bank\n1F3E8 - \; fully-qualified # 🏨 E2.0 hotel\n1F3E9 - \; fully-qualified # 🏩 E2.0 love hotel\n1F3EA - \; fully-qualified # 🏪 E2.0 conve - nience store\n1F3EB \; fully-qualifie - d # 🏫 E2.0 school\n1F3EC \; fu - lly-qualified # 🏬 E2.0 department store\n1F3ED - \; fully-qualified # 🏭 E2.0 factory\n1F3EF - \; fully-qualified # 🏯 E2.0 Japanese - castle\n1F3F0 \; fully-qualified - # 🏰 E2.0 castle\n1F492 \; fully-qu - alified # 💒 E2.0 wedding\n1F5FC - \; fully-qualified # 🗼 E2.0 Tokyo tower\n1F5FD - \; fully-qualified # 🗽 E2.0 Statue of Liberty\n\ - n# subgroup: place-religious\n26EA \ - ; fully-qualified # ⛪ E2.0 church\n1F54C - \; fully-qualified # 🕌 E2.0 mosque\n1F6D5 - \; fully-qualified # 🛕 E12.1 hindu temple\n1F - 54D \; fully-qualified # 🕍 E2. - 0 synagogue\n26E9 FE0F \; fully-qualified - # ⛩️ E2.0 shinto shrine\n26E9 - \; unqualified # ⛩ E2.0 shinto shrine\n1F54B - \; fully-qualified # 🕋 E2.0 kaaba\n\n# subgrou - p: place-other\n26F2 \; fully-qualif - ied # ⛲ E2.0 fountain\n26FA \; - fully-qualified # ⛺ E2.0 tent\n1F301 - \; fully-qualified # 🌁 E2.0 foggy\n1F303 - \; fully-qualified # 🌃 E2.0 night with stars\n1F3 - D9 FE0F \; fully-qualified # 🏙️ E - 2.0 cityscape\n1F3D9 \; unqualified - # 🏙 E2.0 cityscape\n1F304 \ - ; fully-qualified # 🌄 E2.0 sunrise over mountains\n1F305 - \; fully-qualified # 🌅 E2.0 sunrise\n1F3 - 06 \; fully-qualified # 🌆 E2.0 - cityscape at dusk\n1F307 \; fully-qu - alified # 🌇 E2.0 sunset\n1F309 - \; fully-qualified # 🌉 E2.0 bridge at night\n2668 FE0F - \; fully-qualified # ♨️ E2.0 hot springs\n26 - 68 \; unqualified # ♨ E2.0 - hot springs\n1F3A0 \; fully-qualifie - d # 🎠 E2.0 carousel horse\n1F3A1 - \; fully-qualified # 🎡 E2.0 ferris wheel\n1F3A2 - \; fully-qualified # 🎢 E2.0 roller coaster\n1F - 488 \; fully-qualified # �� E - 2.0 barber pole\n1F3AA \; fully-quali - fied # 🎪 E2.0 circus tent\n\n# subgroup: transport-ground\n1F682 - \; fully-qualified # 🚂 E2.0 loco - motive\n1F683 \; fully-qualified - # 🚃 E2.0 railway car\n1F684 \; ful - ly-qualified # 🚄 E2.0 high-speed train\n1F685 - \; fully-qualified # 🚅 E2.0 bullet train\n1F686 - \; fully-qualified # 🚆 E2.0 train - \n1F687 \; fully-qualified # 🚇 - E2.0 metro\n1F688 \; fully-qualified - # 🚈 E2.0 light rail\n1F689 \; - fully-qualified # �� E2.0 station\n1F68A - \; fully-qualified # 🚊 E2.0 tram\n1F69D - \; fully-qualified # 🚝 E2.0 monorail\n1F69E - \; fully-qualified # 🚞 E2.0 mo - untain railway\n1F68B \; fully-qualif - ied # 🚋 E2.0 tram car\n1F68C \ - ; fully-qualified # 🚌 E2.0 bus\n1F68D - \; fully-qualified # 🚍 E2.0 oncoming bus\n1F68E - \; fully-qualified # 🚎 E2.0 trolleybus\n1 - F690 \; fully-qualified # 🚐 E2 - .0 minibus\n1F691 \; fully-qualified - # 🚑 E2.0 ambulance\n1F692 \; f - ully-qualified # 🚒 E2.0 fire engine\n1F693 - \; fully-qualified # 🚓 E2.0 police car\n1F694 - \; fully-qualified # 🚔 E2.0 oncoming p - olice car\n1F695 \; fully-qualified - # 🚕 E2.0 taxi\n1F696 \; fully-q - ualified # 🚖 E2.0 oncoming taxi\n1F697 - \; fully-qualified # 🚗 E2.0 automobile\n1F698 - \; fully-qualified # 🚘 E2.0 oncoming autom - obile\n1F699 \; fully-qualified # - 🚙 E2.0 sport utility vehicle\n1F69A - \; fully-qualified # 🚚 E2.0 delivery truck\n1F69B - \; fully-qualified # 🚛 E2.0 articulated lorr - y\n1F69C \; fully-qualified # - 🚜 E2.0 tractor\n1F3CE FE0F \; fully-qua - lified # 🏎️ E2.0 racing car\n1F3CE - \; unqualified # 🏎 E2.0 racing car\n1F3CD FE0F - \; fully-qualified # 🏍️ E2.0 motorcycle\n1 - F3CD \; unqualified # 🏍 E2 - .0 motorcycle\n1F6F5 \; fully-qualifi - ed # 🛵 E4.0 motor scooter\n1F9BD - \; fully-qualified # 🦽 E12.1 manual wheelchair\n1F9BC - \; fully-qualified # 🦼 E12.1 motorized w - heelchair\n1F6FA \; fully-qualified - # 🛺 E12.1 auto rickshaw\n1F6B2 - \; fully-qualified # 🚲 E2.0 bicycle\n1F6F4 - \; fully-qualified # 🛴 E4.0 kick scooter\n1F6F9 - \; fully-qualified # 🛹 E11.0 skatebo - ard\n1F68F \; fully-qualified # - 🚏 E2.0 bus stop\n1F6E3 FE0F \; fully-qu - alified # 🛣️ E2.0 motorway\n1F6E3 - \; unqualified # 🛣 E2.0 motorway\n1F6E4 FE0F - \; fully-qualified # 🛤️ E2.0 railway track\n1 - F6E4 \; unqualified # 🛤 E2 - .0 railway track\n1F6E2 FE0F \; fully-qual - ified # 🛢️ E2.0 oil drum\n1F6E2 - \; unqualified # 🛢 E2.0 oil drum\n26FD - \; fully-qualified # ⛽ E2.0 fuel pump\n1F6A8 - \; fully-qualified # 🚨 E2.0 police - car light\n1F6A5 \; fully-qualified - # 🚥 E2.0 horizontal traffic light\n1F6A6 - \; fully-qualified # 🚦 E2.0 vertical traffic light\n1F6D1 - \; fully-qualified # 🛑 E4.0 s - top sign\n1F6A7 \; fully-qualified - # 🚧 E2.0 construction\n\n# subgroup: transport-water\n2693 - \; fully-qualified # ⚓ E2.0 anchor\n26F5 - \; fully-qualified # ⛵ E2.0 s - ailboat\n1F6F6 \; fully-qualified - # 🛶 E4.0 canoe\n1F6A4 \; fully-qu - alified # 🚤 E2.0 speedboat\n1F6F3 FE0F - \; fully-qualified # 🛳️ E2.0 passenger ship\n1F6F3 - \; unqualified # 🛳 E2.0 passenger sh - ip\n26F4 FE0F \; fully-qualified # - ⛴️ E2.0 ferry\n26F4 \; unqualifi - ed # ⛴ E2.0 ferry\n1F6E5 FE0F \; - fully-qualified # 🛥️ E2.0 motor boat\n1F6E5 - \; unqualified # 🛥 E2.0 motor boat\n1F6A2 - \; fully-qualified # 🚢 E2.0 ship\n - \n# subgroup: transport-air\n2708 FE0F \; - fully-qualified # ✈️ E2.0 airplane\n2708 - \; unqualified # ✈ E2.0 airplane\n1F6E9 FE0F - \; fully-qualified # 🛩️ E2.0 small air - plane\n1F6E9 \; unqualified # - 🛩 E2.0 small airplane\n1F6EB \; f - ully-qualified # �� E2.0 airplane departure\n1F6EC - \; fully-qualified # 🛬 E2.0 airplane arrival\ - n1FA82 \; fully-qualified # 🪂 - E12.1 parachute\n1F4BA \; fully-quali - fied # 💺 E2.0 seat\n1F681 \; f - ully-qualified # 🚁 E2.0 helicopter\n1F69F - \; fully-qualified # 🚟 E2.0 suspension railway\n1F6A0 - \; fully-qualified # 🚠 E2.0 mou - ntain cableway\n1F6A1 \; fully-qualif - ied # 🚡 E2.0 aerial tramway\n1F6F0 FE0F - \; fully-qualified # 🛰️ E2.0 satellite\n1F6F0 - \; unqualified # 🛰 E2.0 satellite\n1F680 - \; fully-qualified # 🚀 E2.0 r - ocket\n1F6F8 \; fully-qualified # - 🛸 E5.0 flying saucer\n\n# subgroup: hotel\n1F6CE FE0F - \; fully-qualified # 🛎️ E2.0 bellhop bell\n1F6CE - \; unqualified # 🛎 E2.0 be - llhop bell\n1F9F3 \; fully-qualified - # 🧳 E11.0 luggage\n\n# subgroup: time\n231B - \; fully-qualified # ⌛ E2.0 hourglass done\n23F3 - \; fully-qualified # ⏳ E2.0 hourgl - ass not done\n231A \; fully-qualifie - d # ⌚ E2.0 watch\n23F0 \; full - y-qualified # ⏰ E2.0 alarm clock\n23F1 FE0F - \; fully-qualified # ⏱️ E2.0 stopwatch\n23F1 - \; unqualified # ⏱ E2.0 stopwatch\n23F - 2 FE0F \; fully-qualified # ⏲️ E2 - .0 timer clock\n23F2 \; unqualified - # ⏲ E2.0 timer clock\n1F570 FE0F - \; fully-qualified # 🕰️ E2.0 mantelpiece clock\n1F570 - \; unqualified # 🕰 E2.0 mantelpiece - clock\n1F55B \; fully-qualified # - 🕛 E2.0 twelve o’clock\n1F567 \; - fully-qualified # 🕧 E2.0 twelve-thirty\n1F550 - \; fully-qualified # 🕐 E2.0 one o’clock\n1F55C - \; fully-qualified # 🕜 E2.0 one - -thirty\n1F551 \; fully-qualified - # 🕑 E2.0 two o’clock\n1F55D \; - fully-qualified # 🕝 E2.0 two-thirty\n1F552 - \; fully-qualified # 🕒 E2.0 three o’clock\n1F55E - \; fully-qualified # 🕞 E2.0 three - -thirty\n1F553 \; fully-qualified - # 🕓 E2.0 four o’clock\n1F55F \; - fully-qualified # 🕟 E2.0 four-thirty\n1F554 - \; fully-qualified # 🕔 E2.0 five o’clock\n1F560 - \; fully-qualified # 🕠 E2.0 five - -thirty\n1F555 \; fully-qualified - # 🕕 E2.0 six o’clock\n1F561 \; - fully-qualified # 🕡 E2.0 six-thirty\n1F556 - \; fully-qualified # 🕖 E2.0 seven o’clock\n1F562 - \; fully-qualified # 🕢 E2.0 seven - -thirty\n1F557 \; fully-qualified - # 🕗 E2.0 eight o’clock\n1F563 \ - ; fully-qualified # 🕣 E2.0 eight-thirty\n1F558 - \; fully-qualified # 🕘 E2.0 nine o’clock\n1F564 - \; fully-qualified # 🕤 E2.0 ni - ne-thirty\n1F559 \; fully-qualified - # 🕙 E2.0 ten o’clock\n1F565 \ - ; fully-qualified # 🕥 E2.0 ten-thirty\n1F55A - \; fully-qualified # 🕚 E2.0 eleven o’clock\n1F566 - \; fully-qualified # 🕦 E2.0 el - even-thirty\n\n# subgroup: sky & weather\n1F311 - \; fully-qualified # 🌑 E2.0 new moon\n1F312 - \; fully-qualified # 🌒 E2.0 waxing crescen - t moon\n1F313 \; fully-qualified - # 🌓 E2.0 first quarter moon\n1F314 - \; fully-qualified # 🌔 E2.0 waxing gibbous moon\n1F315 - \; fully-qualified # 🌕 E2.0 full moon\n1F - 316 \; fully-qualified # 🌖 E2. - 0 waning gibbous moon\n1F317 \; fully - -qualified # 🌗 E2.0 last quarter moon\n1F318 - \; fully-qualified # 🌘 E2.0 waning crescent moon\n1F - 319 \; fully-qualified # 🌙 E2. - 0 crescent moon\n1F31A \; fully-quali - fied # 🌚 E2.0 new moon face\n1F31B - \; fully-qualified # 🌛 E2.0 first quarter moon face\n1F31C - \; fully-qualified # 🌜 E2.0 last - quarter moon face\n1F321 FE0F \; fully-qua - lified # 🌡️ E2.0 thermometer\n1F321 - \; unqualified # 🌡 E2.0 thermometer\n2600 FE0F - \; fully-qualified # ☀️ E2.0 sun\n2600 - \; unqualified # ☀ E2.0 sun\ - n1F31D \; fully-qualified # 🌝 - E2.0 full moon face\n1F31E \; fully-q - ualified # 🌞 E2.0 sun with face\n1FA90 - \; fully-qualified # 🪐 E12.1 ringed planet\n2B50 - \; fully-qualified # ⭐ E2.0 star\n1F31F - \; fully-qualified # 🌟 E2.0 g - lowing star\n1F320 \; fully-qualified - # 🌠 E2.0 shooting star\n1F30C - \; fully-qualified # 🌌 E2.0 milky way\n2601 FE0F - \; fully-qualified # ☁️ E2.0 cloud\n2601 - \; unqualified # ☁ E2.0 cloud\n26C5 - \; fully-qualified # ⛅ E2.0 s - un behind cloud\n26C8 FE0F \; fully-quali - fied # ⛈️ E2.0 cloud with lightning and rain\n26C8 - \; unqualified # ⛈ E2.0 cloud with lightn - ing and rain\n1F324 FE0F \; fully-qualifie - d # 🌤️ E2.0 sun behind small cloud\n1F324 - \; unqualified # 🌤 E2.0 sun behind small cloud\n1 - F325 FE0F \; fully-qualified # 🌥️ - E2.0 sun behind large cloud\n1F325 \ - ; unqualified # 🌥 E2.0 sun behind large cloud\n1F326 FE0F - \; fully-qualified # 🌦️ E2.0 sun behin - d rain cloud\n1F326 \; unqualified - # 🌦 E2.0 sun behind rain cloud\n1F327 FE0F - \; fully-qualified # 🌧️ E2.0 cloud with rain\n1F327 - \; unqualified # 🌧 E2.0 cloud - with rain\n1F328 FE0F \; fully-qualified - # 🌨️ E2.0 cloud with snow\n1F328 - \; unqualified # 🌨 E2.0 cloud with snow\n1F329 FE0F - \; fully-qualified # 🌩️ E2.0 cloud with - lightning\n1F329 \; unqualified - # 🌩 E2.0 cloud with lightning\n1F32A FE0F - \; fully-qualified # 🌪️ E2.0 tornado\n1F32A - \; unqualified # 🌪 E2.0 tornado\n1F32B F - E0F \; fully-qualified # 🌫️ E2.0 - fog\n1F32B \; unqualified # - 🌫 E2.0 fog\n1F32C FE0F \; fully-qualifi - ed # 🌬️ E2.0 wind face\n1F32C - \; unqualified # 🌬 E2.0 wind face\n1F300 - \; fully-qualified # 🌀 E2.0 cyclone\n1F308 - \; fully-qualified # 🌈 E2.0 rainbow\n - 1F302 \; fully-qualified # 🌂 E - 2.0 closed umbrella\n2602 FE0F \; fully-q - ualified # ☂️ E2.0 umbrella\n2602 - \; unqualified # ☂ E2.0 umbrella\n2614 - \; fully-qualified # ☔ E2.0 umbrella with rain dr - ops\n26F1 FE0F \; fully-qualified # - ⛱️ E2.0 umbrella on ground\n26F1 - \; unqualified # ⛱ E2.0 umbrella on ground\n26A1 - \; fully-qualified # ⚡ E2.0 high voltage\n27 - 44 FE0F \; fully-qualified # ❄️ E - 2.0 snowflake\n2744 \; unqualified - # ❄ E2.0 snowflake\n2603 FE0F \; - fully-qualified # ☃️ E2.0 snowman\n2603 - \; unqualified # ☃ E2.0 snowman\n26C4 - \; fully-qualified # ⛄ E2.0 snowman without - snow\n2604 FE0F \; fully-qualified # - ☄️ E2.0 comet\n2604 \; unqualif - ied # ☄ E2.0 comet\n1F525 \ - ; fully-qualified # 🔥 E2.0 fire\n1F4A7 - \; fully-qualified # 💧 E2.0 droplet\n1F30A - \; fully-qualified # 🌊 E2.0 water wave\n\n# T - ravel & Places subtotal: 259\n# Travel & Places subtotal: 259 w/o modifi - ers\n\n# group: Activities\n\n# subgroup: event\n1F383 - \; fully-qualified # 🎃 E2.0 jack-o-lantern\n1F384 - \; fully-qualified # 🎄 E2.0 C - hristmas tree\n1F386 \; fully-qualifi - ed # 🎆 E2.0 fireworks\n1F387 \ - ; fully-qualified # 🎇 E2.0 sparkler\n1F9E8 - \; fully-qualified # 🧨 E11.0 firecracker\n2728 - \; fully-qualified # ✨ E2.0 sparkles\ - n1F388 \; fully-qualified # 🎈 - E2.0 balloon\n1F389 \; fully-qualifie - d # 🎉 E2.0 party popper\n1F38A - \; fully-qualified # 🎊 E2.0 confetti ball\n1F38B - \; fully-qualified # 🎋 E2.0 tanabata tree\n1F38 - D \; fully-qualified # 🎍 E2.0 - pine decoration\n1F38E \; fully-quali - fied # 🎎 E2.0 Japanese dolls\n1F38F - \; fully-qualified # 🎏 E2.0 carp streamer\n1F390 - \; fully-qualified # 🎐 E2.0 wind chime\n1F - 391 \; fully-qualified # 🎑 E2. - 0 moon viewing ceremony\n1F9E7 \; ful - ly-qualified # 🧧 E11.0 red envelope\n1F380 - \; fully-qualified # 🎀 E2.0 ribbon\n1F381 - \; fully-qualified # 🎁 E2.0 wrapped gift\n - 1F397 FE0F \; fully-qualified # 🎗 - ️ E2.0 reminder ribbon\n1F397 \; un - qualified # 🎗 E2.0 reminder ribbon\n1F39F FE0F - \; fully-qualified # 🎟️ E2.0 admission tickets\n1 - F39F \; unqualified # 🎟 E2 - .0 admission tickets\n1F3AB \; fully- - qualified # 🎫 E2.0 ticket\n\n# subgroup: award-medal\n1F396 FE0F - \; fully-qualified # 🎖️ E2.0 milita - ry medal\n1F396 \; unqualified - # 🎖 E2.0 military medal\n1F3C6 \ - ; fully-qualified # 🏆 E2.0 trophy\n1F3C5 - \; fully-qualified # 🏅 E2.0 sports medal\n1F947 - \; fully-qualified # 🥇 E4.0 1st place - medal\n1F948 \; fully-qualified # - 🥈 E4.0 2nd place medal\n1F949 \; - fully-qualified # 🥉 E4.0 3rd place medal\n\n# subgroup: sport\n26BD - \; fully-qualified # ⚽ E2.0 s - occer ball\n26BE \; fully-qualified - # ⚾ E2.0 baseball\n1F94E \; ful - ly-qualified # 🥎 E11.0 softball\n1F3C0 - \; fully-qualified # 🏀 E2.0 basketball\n1F3D0 - \; fully-qualified # 🏐 E2.0 volleyball\n1F - 3C8 \; fully-qualified # �� E - 2.0 american football\n1F3C9 \; fully - -qualified # 🏉 E2.0 rugby football\n1F3BE - \; fully-qualified # 🎾 E2.0 tennis\n1F94F - \; fully-qualified # 🥏 E11.0 flying disc\n1 - F3B3 \; fully-qualified # 🎳 E2 - .0 bowling\n1F3CF \; fully-qualified - # 🏏 E2.0 cricket game\n1F3D1 \ - ; fully-qualified # 🏑 E2.0 field hockey\n1F3D2 - \; fully-qualified # 🏒 E2.0 ice hockey\n1F94D - \; fully-qualified # 🥍 E11.0 lacro - sse\n1F3D3 \; fully-qualified # - 🏓 E2.0 ping pong\n1F3F8 \; fully-q - ualified # 🏸 E2.0 badminton\n1F94A - \; fully-qualified # �� E4.0 boxing glove\n1F94B - \; fully-qualified # 🥋 E4.0 martial arts u - niform\n1F945 \; fully-qualified - # 🥅 E4.0 goal net\n26F3 \; fully- - qualified # ⛳ E2.0 flag in hole\n26F8 FE0F - \; fully-qualified # ⛸️ E2.0 ice skate\n26F8 - \; unqualified # ⛸ E2.0 ice skate\n1F3A - 3 \; fully-qualified # 🎣 E2.0 - fishing pole\n1F93F \; fully-qualifie - d # 🤿 E12.1 diving mask\n1F3BD - \; fully-qualified # 🎽 E2.0 running shirt\n1F3BF - \; fully-qualified # 🎿 E2.0 skis\n1F6F7 - \; fully-qualified # 🛷 E5.0 sled\n1F9 - 4C \; fully-qualified # 🥌 E5.0 - curling stone\n\n# subgroup: game\n1F3AF - \; fully-qualified # 🎯 E2.0 direct hit\n1FA80 - \; fully-qualified # 🪀 E12.1 yo-yo\n1FA81 - \; fully-qualified # 🪁 E12.1 kite\ - n1F3B1 \; fully-qualified # 🎱 - E2.0 pool 8 ball\n1F52E \; fully-qual - ified # 🔮 E2.0 crystal ball\n1F9FF - \; fully-qualified # 🧿 E11.0 nazar amulet\n1F3AE - \; fully-qualified # 🎮 E2.0 video game\n1F5 - 79 FE0F \; fully-qualified # 🕹️ E - 2.0 joystick\n1F579 \; unqualified - # 🕹 E2.0 joystick\n1F3B0 \; - fully-qualified # 🎰 E2.0 slot machine\n1F3B2 - \; fully-qualified # 🎲 E2.0 game die\n1F9E9 - \; fully-qualified # 🧩 E11.0 puzzle pi - ece\n1F9F8 \; fully-qualified # - 🧸 E11.0 teddy bear\n2660 FE0F \; fully - -qualified # ♠️ E2.0 spade suit\n2660 - \; unqualified # ♠ E2.0 spade suit\n2665 FE0F - \; fully-qualified # ♥️ E2.0 heart suit\n - 2665 \; unqualified # ♥ E2 - .0 heart suit\n2666 FE0F \; fully-qualifi - ed # ♦️ E2.0 diamond suit\n2666 - \; unqualified # ♦ E2.0 diamond suit\n2663 FE0F - \; fully-qualified # ♣️ E2.0 club suit\n2663 - \; unqualified # ♣ E2.0 cl - ub suit\n265F FE0F \; fully-qualified - # ♟️ E11.0 chess pawn\n265F \; - unqualified # ♟ E11.0 chess pawn\n1F0CF - \; fully-qualified # 🃏 E2.0 joker\n1F004 - \; fully-qualified # 🀄 E2.0 mahjong red dra - gon\n1F3B4 \; fully-qualified # - 🎴 E2.0 flower playing cards\n\n# subgroup: arts & crafts\n1F3AD - \; fully-qualified # 🎭 E2.0 performin - g arts\n1F5BC FE0F \; fully-qualified - # 🖼️ E2.0 framed picture\n1F5BC - \; unqualified # 🖼 E2.0 framed picture\n1F3A8 - \; fully-qualified # 🎨 E2.0 artist palette\n1F9 - F5 \; fully-qualified # 🧵 E11. - 0 thread\n1F9F6 \; fully-qualified - # 🧶 E11.0 yarn\n\n# Activities subtotal: 90\n# Activities subtotal: - 90 w/o modifiers\n\n# group: Objects\n\n# subgroup: clothing\n1F453 - \; fully-qualified # 👓 E2.0 glasses - \n1F576 FE0F \; fully-qualified # 🕶 - ️ E2.0 sunglasses\n1F576 \; unquali - fied # 🕶 E2.0 sunglasses\n1F97D - \; fully-qualified # 🥽 E11.0 goggles\n1F97C - \; fully-qualified # 🥼 E11.0 lab coat\n1F9BA - \; fully-qualified # 🦺 E12.1 saf - ety vest\n1F454 \; fully-qualified - # 👔 E2.0 necktie\n1F455 \; fully - -qualified # 👕 E2.0 t-shirt\n1F456 - \; fully-qualified # 👖 E2.0 jeans\n1F9E3 - \; fully-qualified # 🧣 E5.0 scarf\n1F9E4 - \; fully-qualified # 🧤 E5.0 gloves\n1F9E - 5 \; fully-qualified # 🧥 E5.0 - coat\n1F9E6 \; fully-qualified # - 🧦 E5.0 socks\n1F457 \; fully-quali - fied # 👗 E2.0 dress\n1F458 \; - fully-qualified # 👘 E2.0 kimono\n1F97B - \; fully-qualified # 🥻 E12.1 sari\n1FA71 - \; fully-qualified # 🩱 E12.1 one-piece swimsuit - \n1FA72 \; fully-qualified # 🩲 - E12.1 briefs\n1FA73 \; fully-qualifi - ed # 🩳 E12.1 shorts\n1F459 \; - fully-qualified # 👙 E2.0 bikini\n1F45A - \; fully-qualified # 👚 E2.0 woman’s clothes\n1F45B - \; fully-qualified # 👛 E2.0 purse\n - 1F45C \; fully-qualified # 👜 E - 2.0 handbag\n1F45D \; fully-qualified - # 👝 E2.0 clutch bag\n1F6CD FE0F \; - fully-qualified # 🛍️ E2.0 shopping bags\n1F6CD - \; unqualified # 🛍 E2.0 shopping bags\n1F39 - 2 \; fully-qualified # 🎒 E2.0 - backpack\n1F45E \; fully-qualified - # 👞 E2.0 man’s shoe\n1F45F \; - fully-qualified # 👟 E2.0 running shoe\n1F97E - \; fully-qualified # 🥾 E11.0 hiking boot\n1F97F - \; fully-qualified # 🥿 E11.0 flat - shoe\n1F460 \; fully-qualified # - 👠 E2.0 high-heeled shoe\n1F461 \; - fully-qualified # 👡 E2.0 woman’s sandal\n1FA70 - \; fully-qualified # 🩰 E12.1 ballet shoes\n1F462 - \; fully-qualified # 👢 E2.0 w - oman’s boot\n1F451 \; fully-qualifi - ed # 👑 E2.0 crown\n1F452 \; fu - lly-qualified # 👒 E2.0 woman’s hat\n1F3A9 - \; fully-qualified # 🎩 E2.0 top hat\n1F393 - \; fully-qualified # 🎓 E2.0 graduation c - ap\n1F9E2 \; fully-qualified # - 🧢 E5.0 billed cap\n26D1 FE0F \; fully- - qualified # ⛑️ E2.0 rescue worker’s helmet\n26D1 - \; unqualified # ⛑ E2.0 rescue worker’s - helmet\n1F4FF \; fully-qualified - # 📿 E2.0 prayer beads\n1F484 \; f - ully-qualified # 💄 E2.0 lipstick\n1F48D - \; fully-qualified # 💍 E2.0 ring\n1F48E - \; fully-qualified # 💎 E2.0 gem stone\n\n# subg - roup: sound\n1F507 \; fully-qualified - # 🔇 E2.0 muted speaker\n1F508 - \; fully-qualified # 🔈 E2.0 speaker low volume\n1F509 - \; fully-qualified # 🔉 E2.0 speaker medium - volume\n1F50A \; fully-qualified - # 🔊 E2.0 speaker high volume\n1F4E2 - \; fully-qualified # 📢 E2.0 loudspeaker\n1F4E3 - \; fully-qualified # 📣 E2.0 megaphone\n1F4EF - \; fully-qualified # 📯 E2.0 post - al horn\n1F514 \; fully-qualified - # 🔔 E2.0 bell\n1F515 \; fully-qua - lified # 🔕 E2.0 bell with slash\n\n# subgroup: music\n1F3BC - \; fully-qualified # 🎼 E2.0 musical s - core\n1F3B5 \; fully-qualified # - 🎵 E2.0 musical note\n1F3B6 \; full - y-qualified # 🎶 E2.0 musical notes\n1F399 FE0F - \; fully-qualified # 🎙️ E2.0 studio microphone\n1F399 - \; unqualified # 🎙 E2.0 s - tudio microphone\n1F39A FE0F \; fully-qual - ified # 🎚️ E2.0 level slider\n1F39A - \; unqualified # 🎚 E2.0 level slider\n1F39B FE0F - \; fully-qualified # 🎛️ E2.0 control kn - obs\n1F39B \; unqualified # - 🎛 E2.0 control knobs\n1F3A4 \; ful - ly-qualified # 🎤 E2.0 microphone\n1F3A7 - \; fully-qualified # 🎧 E2.0 headphone\n1F4FB - \; fully-qualified # 📻 E2.0 radio\n\n# sub - group: musical-instrument\n1F3B7 \; f - ully-qualified # 🎷 E2.0 saxophone\n1F3B8 - \; fully-qualified # 🎸 E2.0 guitar\n1F3B9 - \; fully-qualified # 🎹 E2.0 musical keyboard - \n1F3BA \; fully-qualified # 🎺 - E2.0 trumpet\n1F3BB \; fully-qualifi - ed # 🎻 E2.0 violin\n1FA95 \; f - ully-qualified # 🪕 E12.1 banjo\n1F941 - \; fully-qualified # 🥁 E4.0 drum\n\n# subgroup: phone\n1F4F - 1 \; fully-qualified # 📱 E2.0 - mobile phone\n1F4F2 \; fully-qualifie - d # 📲 E2.0 mobile phone with arrow\n260E FE0F - \; fully-qualified # ☎️ E2.0 telephone\n260E - \; unqualified # ☎ E2.0 telephone\n - 1F4DE \; fully-qualified # 📞 E - 2.0 telephone receiver\n1F4DF \; full - y-qualified # 📟 E2.0 pager\n1F4E0 - \; fully-qualified # 📠 E2.0 fax machine\n\n# subgroup: computer - \n1F50B \; fully-qualified # 🔋 - E2.0 battery\n1F50C \; fully-qualifi - ed # 🔌 E2.0 electric plug\n1F4BB - \; fully-qualified # 💻 E2.0 laptop\n1F5A5 FE0F - \; fully-qualified # 🖥️ E2.0 desktop computer\n1F5 - A5 \; unqualified # 🖥 E2.0 - desktop computer\n1F5A8 FE0F \; fully-qua - lified # 🖨️ E2.0 printer\n1F5A8 - \; unqualified # 🖨 E2.0 printer\n2328 FE0F - \; fully-qualified # ⌨️ E2.0 keyboard\n2328 - \; unqualified # ⌨ E2.0 keyboar - d\n1F5B1 FE0F \; fully-qualified # - 🖱️ E2.0 computer mouse\n1F5B1 \; - unqualified # 🖱 E2.0 computer mouse\n1F5B2 FE0F - \; fully-qualified # 🖲️ E2.0 trackball\n1F5B2 - \; unqualified # 🖲 E2.0 tra - ckball\n1F4BD \; fully-qualified - # 💽 E2.0 computer disk\n1F4BE \; f - ully-qualified # 💾 E2.0 floppy disk\n1F4BF - \; fully-qualified # 💿 E2.0 optical disk\n1F4C0 - \; fully-qualified # 📀 E2.0 dvd\n1F9 - EE \; fully-qualified # 🧮 E11. - 0 abacus\n\n# subgroup: light & video\n1F3A5 - \; fully-qualified # 🎥 E2.0 movie camera\n1F39E FE0F - \; fully-qualified # 🎞️ E2.0 film frame - s\n1F39E \; unqualified # - 🎞 E2.0 film frames\n1F4FD FE0F \; fully - -qualified # 📽️ E2.0 film projector\n1F4FD - \; unqualified # 📽 E2.0 film projector\n1F3AC - \; fully-qualified # 🎬 E2.0 clap - per board\n1F4FA \; fully-qualified - # 📺 E2.0 television\n1F4F7 \; f - ully-qualified # 📷 E2.0 camera\n1F4F8 - \; fully-qualified # 📸 E2.0 camera with flash\n1F4F9 - \; fully-qualified # 📹 E2.0 video ca - mera\n1F4FC \; fully-qualified # - �� E2.0 videocassette\n1F50D \; f - ully-qualified # 🔍 E2.0 magnifying glass tilted left\n1F50E - \; fully-qualified # 🔎 E2.0 magnifyin - g glass tilted right\n1F56F FE0F \; fully- - qualified # 🕯️ E2.0 candle\n1F56F - \; unqualified # 🕯 E2.0 candle\n1F4A1 - \; fully-qualified # 💡 E2.0 light bulb\n1F526 - \; fully-qualified # 🔦 E2.0 flash - light\n1F3EE \; fully-qualified # - 🏮 E2.0 red paper lantern\n1FA94 \ - ; fully-qualified # 🪔 E12.1 diya lamp\n\n# subgroup: book-paper\n1F - 4D4 \; fully-qualified # 📔 E2. - 0 notebook with decorative cover\n1F4D5 - \; fully-qualified # 📕 E2.0 closed book\n1F4D6 - \; fully-qualified # 📖 E2.0 open book\n1F4D7 - \; fully-qualified # 📗 E2.0 gree - n book\n1F4D8 \; fully-qualified - # 📘 E2.0 blue book\n1F4D9 \; fully - -qualified # 📙 E2.0 orange book\n1F4DA - \; fully-qualified # 📚 E2.0 books\n1F4D3 - \; fully-qualified # 📓 E2.0 notebook\n1F4D2 - \; fully-qualified # �� E2.0 led - ger\n1F4C3 \; fully-qualified # - 📃 E2.0 page with curl\n1F4DC \; fu - lly-qualified # 📜 E2.0 scroll\n1F4C4 - \; fully-qualified # 📄 E2.0 page facing up\n1F4F0 - \; fully-qualified # 📰 E2.0 newspaper\n1 - F5DE FE0F \; fully-qualified # 🗞️ - E2.0 rolled-up newspaper\n1F5DE \; u - nqualified # 🗞 E2.0 rolled-up newspaper\n1F4D1 - \; fully-qualified # 📑 E2.0 bookmark tabs\n1F5 - 16 \; fully-qualified # 🔖 E2.0 - bookmark\n1F3F7 FE0F \; fully-qualified - # 🏷️ E2.0 label\n1F3F7 \; unq - ualified # 🏷 E2.0 label\n\n# subgroup: money\n1F4B0 - \; fully-qualified # 💰 E2.0 money bag\n1F - 4B4 \; fully-qualified # 💴 E2. - 0 yen banknote\n1F4B5 \; fully-qualif - ied # 💵 E2.0 dollar banknote\n1F4B6 - \; fully-qualified # 💶 E2.0 euro banknote\n1F4B7 - \; fully-qualified # 💷 E2.0 pound banknote - \n1F4B8 \; fully-qualified # 💸 - E2.0 money with wings\n1F4B3 \; full - y-qualified # 💳 E2.0 credit card\n1F9FE - \; fully-qualified # 🧾 E11.0 receipt\n1F4B9 - \; fully-qualified # 💹 E2.0 chart increasin - g with yen\n1F4B1 \; fully-qualified - # 💱 E2.0 currency exchange\n1F4B2 - \; fully-qualified # 💲 E2.0 heavy dollar sign\n\n# subgroup: ma - il\n2709 FE0F \; fully-qualified # - ✉️ E2.0 envelope\n2709 \; unqual - ified # ✉ E2.0 envelope\n1F4E7 - \; fully-qualified # 📧 E2.0 e-mail\n1F4E8 - \; fully-qualified # 📨 E2.0 incoming envelope\n1F4E - 9 \; fully-qualified # 📩 E2.0 - envelope with arrow\n1F4E4 \; fully-q - ualified # 📤 E2.0 outbox tray\n1F4E5 - \; fully-qualified # 📥 E2.0 inbox tray\n1F4E6 - \; fully-qualified # 📦 E2.0 package\n1F4EB - \; fully-qualified # 📫 E2.0 clo - sed mailbox with raised flag\n1F4EA \ - ; fully-qualified # 📪 E2.0 closed mailbox with lowered flag\n1F4EC - \; fully-qualified # 📬 E2.0 op - en mailbox with raised flag\n1F4ED \; - fully-qualified # 📭 E2.0 open mailbox with lowered flag\n1F4EE - \; fully-qualified # 📮 E2.0 postb - ox\n1F5F3 FE0F \; fully-qualified # - 🗳️ E2.0 ballot box with ballot\n1F5F3 - \; unqualified # 🗳 E2.0 ballot box with ballot\n\n# subgr - oup: writing\n270F FE0F \; fully-qualifie - d # ✏️ E2.0 pencil\n270F \; - unqualified # ✏ E2.0 pencil\n2712 FE0F - \; fully-qualified # ✒️ E2.0 black nib\n2712 - \; unqualified # ✒ E2.0 black nib\n1F58 - B FE0F \; fully-qualified # 🖋️ E2 - .0 fountain pen\n1F58B \; unqualified - # 🖋 E2.0 fountain pen\n1F58A FE0F - \; fully-qualified # 🖊️ E2.0 pen\n1F58A - \; unqualified # 🖊 E2.0 pen\n1F58C FE0F - \; fully-qualified # 🖌️ E2.0 paintbrush\ - n1F58C \; unqualified # 🖌 - E2.0 paintbrush\n1F58D FE0F \; fully-quali - fied # 🖍️ E2.0 crayon\n1F58D - \; unqualified # 🖍 E2.0 crayon\n1F4DD - \; fully-qualified # 📝 E2.0 memo\n\n# subgroup: office - \n1F4BC \; fully-qualified # 💼 - E2.0 briefcase\n1F4C1 \; fully-quali - fied # 📁 E2.0 file folder\n1F4C2 - \; fully-qualified # 📂 E2.0 open file folder\n1F5C2 FE0F - \; fully-qualified # 🗂️ E2.0 card index - dividers\n1F5C2 \; unqualified - # 🗂 E2.0 card index dividers\n1F4C5 - \; fully-qualified # 📅 E2.0 calendar\n1F4C6 - \; fully-qualified # 📆 E2.0 tear-off calendar\n1F - 5D2 FE0F \; fully-qualified # 🗒️ - E2.0 spiral notepad\n1F5D2 \; unquali - fied # 🗒 E2.0 spiral notepad\n1F5D3 FE0F - \; fully-qualified # 🗓️ E2.0 spiral calendar\n1F5D3 - \; unqualified # 🗓 E2.0 spira - l calendar\n1F4C7 \; fully-qualified - # 📇 E2.0 card index\n1F4C8 \; - fully-qualified # 📈 E2.0 chart increasing\n1F4C9 - \; fully-qualified # 📉 E2.0 chart decreasing\n1F - 4CA \; fully-qualified # 📊 E2. - 0 bar chart\n1F4CB \; fully-qualified - # 📋 E2.0 clipboard\n1F4CC \; - fully-qualified # 📌 E2.0 pushpin\n1F4CD - \; fully-qualified # 📍 E2.0 round pushpin\n1F4CE - \; fully-qualified # 📎 E2.0 paperclip\ - n1F587 FE0F \; fully-qualified # 🖇 - ️ E2.0 linked paperclips\n1F587 \; - unqualified # 🖇 E2.0 linked paperclips\n1F4CF - \; fully-qualified # 📏 E2.0 straight ruler\n1F4 - D0 \; fully-qualified # 📐 E2.0 - triangular ruler\n2702 FE0F \; fully-qua - lified # ✂️ E2.0 scissors\n2702 - \; unqualified # ✂ E2.0 scissors\n1F5C3 FE0F - \; fully-qualified # 🗃️ E2.0 card file box\n1F5C - 3 \; unqualified # 🗃 E2.0 - card file box\n1F5C4 FE0F \; fully-qualifi - ed # 🗄️ E2.0 file cabinet\n1F5C4 - \; unqualified # 🗄 E2.0 file cabinet\n1F5D1 FE0F - \; fully-qualified # 🗑️ E2.0 wastebasket\n - 1F5D1 \; unqualified # 🗑 E - 2.0 wastebasket\n\n# subgroup: lock\n1F512 - \; fully-qualified # 🔒 E2.0 locked\n1F513 - \; fully-qualified # 🔓 E2.0 unlocked\n1F50F - \; fully-qualified # 🔏 E2.0 locked - with pen\n1F510 \; fully-qualified - # �� E2.0 locked with key\n1F511 - \; fully-qualified # 🔑 E2.0 key\n1F5DD FE0F - \; fully-qualified # 🗝️ E2.0 old key\n1F5DD - \; unqualified # 🗝 E2.0 old key\n\n# - subgroup: tool\n1F528 \; fully-quali - fied # 🔨 E2.0 hammer\n1FA93 \; - fully-qualified # 🪓 E12.1 axe\n26CF FE0F - \; fully-qualified # ⛏️ E2.0 pick\n26CF - \; unqualified # ⛏ E2.0 pick\n2692 FE0F - \; fully-qualified # ⚒️ E2.0 hammer a - nd pick\n2692 \; unqualified - # ⚒ E2.0 hammer and pick\n1F6E0 FE0F \; - fully-qualified # 🛠️ E2.0 hammer and wrench\n1F6E0 - \; unqualified # �� E2.0 hammer and wr - ench\n1F5E1 FE0F \; fully-qualified # - 🗡️ E2.0 dagger\n1F5E1 \; unquali - fied # 🗡 E2.0 dagger\n2694 FE0F - \; fully-qualified # ⚔️ E2.0 crossed swords\n2694 - \; unqualified # ⚔ E2.0 crossed swords\n - 1F52B \; fully-qualified # 🔫 E - 2.0 pistol\n1F3F9 \; fully-qualified - # 🏹 E2.0 bow and arrow\n1F6E1 FE0F - \; fully-qualified # 🛡️ E2.0 shield\n1F6E1 - \; unqualified # 🛡 E2.0 shield\n1F527 - \; fully-qualified # 🔧 E2.0 wrench\n1F52 - 9 \; fully-qualified # 🔩 E2.0 - nut and bolt\n2699 FE0F \; fully-qualifie - d # ⚙️ E2.0 gear\n2699 \; un - qualified # ⚙ E2.0 gear\n1F5DC FE0F - \; fully-qualified # 🗜️ E2.0 clamp\n1F5DC - \; unqualified # 🗜 E2.0 clamp\n2696 FE0F - \; fully-qualified # ⚖️ E2.0 balance - scale\n2696 \; unqualified # - ⚖ E2.0 balance scale\n1F9AF \; ful - ly-qualified # 🦯 E12.1 probing cane\n1F517 - \; fully-qualified # 🔗 E2.0 link\n26D3 FE0F - \; fully-qualified # ⛓️ E2.0 chains\n26D3 - \; unqualified # ⛓ E2.0 cha - ins\n1F9F0 \; fully-qualified # - 🧰 E11.0 toolbox\n1F9F2 \; fully-qu - alified # 🧲 E11.0 magnet\n\n# subgroup: science\n2697 FE0F - \; fully-qualified # ⚗️ E2.0 alembic\n269 - 7 \; unqualified # ⚗ E2.0 - alembic\n1F9EA \; fully-qualified - # 🧪 E11.0 test tube\n1F9EB \; ful - ly-qualified # 🧫 E11.0 petri dish\n1F9EC - \; fully-qualified # 🧬 E11.0 dna\n1F52C - \; fully-qualified # 🔬 E2.0 microscope\n1F52D - \; fully-qualified # 🔭 E2.0 te - lescope\n1F4E1 \; fully-qualified - # 📡 E2.0 satellite antenna\n\n# subgroup: medical\n1F489 - \; fully-qualified # 💉 E2.0 syringe\n1FA78 - \; fully-qualified # 🩸 E12.1 d - rop of blood\n1F48A \; fully-qualifie - d # 💊 E2.0 pill\n1FA79 \; full - y-qualified # 🩹 E12.1 adhesive bandage\n1FA7A - \; fully-qualified # 🩺 E12.1 stethoscope\n\n# subgr - oup: household\n1F6AA \; fully-qualif - ied # 🚪 E2.0 door\n1F6CF FE0F \; fu - lly-qualified # 🛏️ E2.0 bed\n1F6CF - \; unqualified # 🛏 E2.0 bed\n1F6CB FE0F - \; fully-qualified # 🛋️ E2.0 couch and lamp\n1F6C - B \; unqualified # 🛋 E2.0 - couch and lamp\n1FA91 \; fully-qualif - ied # 🪑 E12.1 chair\n1F6BD \; - fully-qualified # 🚽 E2.0 toilet\n1F6BF - \; fully-qualified # 🚿 E2.0 shower\n1F6C1 - \; fully-qualified # 🛁 E2.0 bathtub\n1FA92 - \; fully-qualified # 🪒 E12.1 razo - r\n1F9F4 \; fully-qualified # - 🧴 E11.0 lotion bottle\n1F9F7 \; fu - lly-qualified # 🧷 E11.0 safety pin\n1F9F9 - \; fully-qualified # 🧹 E11.0 broom\n1F9FA - \; fully-qualified # 🧺 E11.0 basket\n1F9FB - \; fully-qualified # 🧻 E11.0 r - oll of paper\n1F9FC \; fully-qualifie - d # 🧼 E11.0 soap\n1F9FD \; ful - ly-qualified # 🧽 E11.0 sponge\n1F9EF - \; fully-qualified # 🧯 E11.0 fire extinguisher\n1F6D2 - \; fully-qualified # 🛒 E4.0 shopping - cart\n\n# subgroup: other-object\n1F6AC - \; fully-qualified # 🚬 E2.0 cigarette\n26B0 FE0F - \; fully-qualified # ⚰️ E2.0 coffin\n26B0 - \; unqualified # ⚰ E2.0 coffin\ - n26B1 FE0F \; fully-qualified # ⚱ - ️ E2.0 funeral urn\n26B1 \; unqual - ified # ⚱ E2.0 funeral urn\n1F5FF - \; fully-qualified # 🗿 E2.0 moai\n\n# Objects subtotal: 282 - \n# Objects subtotal: 282 w/o modifiers\n\n# group: Symbols\n\n# subgroup - : transport-sign\n1F3E7 \; fully-qual - ified # 🏧 E2.0 ATM sign\n1F6AE - \; fully-qualified # 🚮 E2.0 litter in bin sign\n1F6B0 - \; fully-qualified # 🚰 E2.0 potable water\ - n267F \; fully-qualified # ♿ E - 2.0 wheelchair symbol\n1F6B9 \; fully - -qualified # 🚹 E2.0 men’s room\n1F6BA - \; fully-qualified # 🚺 E2.0 women’s room\n1F6BB - \; fully-qualified # 🚻 E2.0 restroom\ - n1F6BC \; fully-qualified # 🚼 - E2.0 baby symbol\n1F6BE \; fully-qual - ified # 🚾 E2.0 water closet\n1F6C2 - \; fully-qualified # 🛂 E2.0 passport control\n1F6C3 - \; fully-qualified # 🛃 E2.0 customs\n1F6 - C4 \; fully-qualified # 🛄 E2.0 - baggage claim\n1F6C5 \; fully-qualif - ied # 🛅 E2.0 left luggage\n\n# subgroup: warning\n26A0 FE0F - \; fully-qualified # ⚠️ E2.0 warning\n26 - A0 \; unqualified # ⚠ E2.0 - warning\n1F6B8 \; fully-qualified - # 🚸 E2.0 children crossing\n26D4 - \; fully-qualified # ⛔ E2.0 no entry\n1F6AB - \; fully-qualified # 🚫 E2.0 prohibited\n1F6B3 - \; fully-qualified # 🚳 E2.0 no bicyc - les\n1F6AD \; fully-qualified # - 🚭 E2.0 no smoking\n1F6AF \; fully- - qualified # 🚯 E2.0 no littering\n1F6B1 - \; fully-qualified # 🚱 E2.0 non-potable water\n1F6B7 - \; fully-qualified # 🚷 E2.0 no pede - strians\n1F4F5 \; fully-qualified - # 📵 E2.0 no mobile phones\n1F51E - \; fully-qualified # 🔞 E2.0 no one under eighteen\n2622 FE0F - \; fully-qualified # ☢️ E2.0 radioactiv - e\n2622 \; unqualified # ☢ - E2.0 radioactive\n2623 FE0F \; fully-qua - lified # ☣️ E2.0 biohazard\n2623 - \; unqualified # ☣ E2.0 biohazard\n\n# subgroup: arrow\n2B0 - 6 FE0F \; fully-qualified # ⬆️ E2 - .0 up arrow\n2B06 \; unqualified - # ⬆ E2.0 up arrow\n2197 FE0F \; fu - lly-qualified # ↗️ E2.0 up-right arrow\n2197 - \; unqualified # ↗ E2.0 up-right arrow\n27A1 FE - 0F \; fully-qualified # ➡️ E2.0 r - ight arrow\n27A1 \; unqualified - # ➡ E2.0 right arrow\n2198 FE0F \; - fully-qualified # ↘️ E2.0 down-right arrow\n2198 - \; unqualified # ↘ E2.0 down-right arrow\n2 - B07 FE0F \; fully-qualified # ⬇️ - E2.0 down arrow\n2B07 \; unqualified - # ⬇ E2.0 down arrow\n2199 FE0F - \; fully-qualified # ↙️ E2.0 down-left arrow\n2199 - \; unqualified # ↙ E2.0 down-left arrow\ - n2B05 FE0F \; fully-qualified # ⬅ - ️ E2.0 left arrow\n2B05 \; unquali - fied # ⬅ E2.0 left arrow\n2196 FE0F - \; fully-qualified # ↖️ E2.0 up-left arrow\n2196 - \; unqualified # ↖ E2.0 up-left arrow\ - n2195 FE0F \; fully-qualified # ↕ - ️ E2.0 up-down arrow\n2195 \; unqu - alified # ↕ E2.0 up-down arrow\n2194 FE0F - \; fully-qualified # ↔️ E2.0 left-right arrow\n2194 - \; unqualified # ↔ E2.0 left- - right arrow\n21A9 FE0F \; fully-qualified - # ↩️ E2.0 right arrow curving left\n21A9 - \; unqualified # ↩ E2.0 right arrow curving left\n - 21AA FE0F \; fully-qualified # ↪️ - E2.0 left arrow curving right\n21AA - \; unqualified # ↪ E2.0 left arrow curving right\n2934 FE0F - \; fully-qualified # ⤴️ E2.0 right a - rrow curving up\n2934 \; unqualified - # ⤴ E2.0 right arrow curving up\n2935 FE0F - \; fully-qualified # ⤵️ E2.0 right arrow curving down - \n2935 \; unqualified # ⤵ - E2.0 right arrow curving down\n1F503 - \; fully-qualified # 🔃 E2.0 clockwise vertical arrows\n1F504 - \; fully-qualified # 🔄 E2.0 counterc - lockwise arrows button\n1F519 \; full - y-qualified # 🔙 E2.0 BACK arrow\n1F51A - \; fully-qualified # 🔚 E2.0 END arrow\n1F51B - \; fully-qualified # 🔛 E2.0 ON! arrow\n1F51 - C \; fully-qualified # 🔜 E2.0 - SOON arrow\n1F51D \; fully-qualified - # 🔝 E2.0 TOP arrow\n\n# subgroup: religion\n1F6D0 - \; fully-qualified # 🛐 E2.0 place of worship\n2 - 69B FE0F \; fully-qualified # ⚛️ - E2.0 atom symbol\n269B \; unqualifie - d # ⚛ E2.0 atom symbol\n1F549 FE0F - \; fully-qualified # 🕉️ E2.0 om\n1F549 - \; unqualified # 🕉 E2.0 om\n2721 FE0F - \; fully-qualified # ✡️ E2.0 star of David\n - 2721 \; unqualified # ✡ E2 - .0 star of David\n2638 FE0F \; fully-qual - ified # ☸️ E2.0 wheel of dharma\n2638 - \; unqualified # ☸ E2.0 wheel of dharma\n262F FE0F - \; fully-qualified # ☯️ E2.0 yin yan - g\n262F \; unqualified # ☯ - E2.0 yin yang\n271D FE0F \; fully-qualif - ied # ✝️ E2.0 latin cross\n271D - \; unqualified # ✝ E2.0 latin cross\n2626 FE0F - \; fully-qualified # ☦️ E2.0 orthodox cross\n2 - 626 \; unqualified # ☦ E2. - 0 orthodox cross\n262A FE0F \; fully-qual - ified # ☪️ E2.0 star and crescent\n262A - \; unqualified # ☪ E2.0 star and crescent\n262E FE0F - \; fully-qualified # ☮️ E2.0 pea - ce symbol\n262E \; unqualified - # ☮ E2.0 peace symbol\n1F54E \; - fully-qualified # 🕎 E2.0 menorah\n1F52F - \; fully-qualified # 🔯 E2.0 dotted six-pointed star\n\n# - subgroup: zodiac\n2648 \; fully-qual - ified # ♈ E2.0 Aries\n2649 \; - fully-qualified # ♉ E2.0 Taurus\n264A - \; fully-qualified # ♊ E2.0 Gemini\n264B - \; fully-qualified # ♋ E2.0 Cancer\n264C - \; fully-qualified # ♌ E2.0 Leo\n264D - \; fully-qualified # ♍ E2.0 Vi - rgo\n264E \; fully-qualified # - ♎ E2.0 Libra\n264F \; fully-qualif - ied # ♏ E2.0 Scorpio\n2650 \; - fully-qualified # ♐ E2.0 Sagittarius\n2651 - \; fully-qualified # ♑ E2.0 Capricorn\n2652 - \; fully-qualified # ♒ E2.0 Aquarius\n265 - 3 \; fully-qualified # ♓ E2.0 - Pisces\n26CE \; fully-qualified - # ⛎ E2.0 Ophiuchus\n\n# subgroup: av-symbol\n1F500 - \; fully-qualified # 🔀 E2.0 shuffle tracks button\n - 1F501 \; fully-qualified # 🔁 E - 2.0 repeat button\n1F502 \; fully-qua - lified # 🔂 E2.0 repeat single button\n25B6 FE0F - \; fully-qualified # ▶️ E2.0 play button\n25B6 - \; unqualified # ▶ E2.0 play bu - tton\n23E9 \; fully-qualified # - ⏩ E2.0 fast-forward button\n23ED FE0F \ - ; fully-qualified # ⏭️ E2.0 next track button\n23ED - \; unqualified # ⏭ E2.0 next track butto - n\n23EF FE0F \; fully-qualified # ⏯ - ️ E2.0 play or pause button\n23EF - \; unqualified # ⏯ E2.0 play or pause button\n25C0 FE0F - \; fully-qualified # ◀️ E2.0 reverse butt - on\n25C0 \; unqualified # - ◀ E2.0 reverse button\n23EA \; ful - ly-qualified # ⏪ E2.0 fast reverse button\n23EE FE0F - \; fully-qualified # ⏮️ E2.0 last track button\n - 23EE \; unqualified # ⏮ E2 - .0 last track button\n1F53C \; fully- - qualified # 🔼 E2.0 upwards button\n23EB - \; fully-qualified # ⏫ E2.0 fast up button\n1F53D - \; fully-qualified # �� E2.0 downwar - ds button\n23EC \; fully-qualified - # ⏬ E2.0 fast down button\n23F8 FE0F - \; fully-qualified # ⏸️ E2.0 pause button\n23F8 - \; unqualified # ⏸ E2.0 pause button\n23F9 - FE0F \; fully-qualified # ⏹️ E2.0 - stop button\n23F9 \; unqualified - # ⏹ E2.0 stop button\n23FA FE0F \ - ; fully-qualified # ⏺️ E2.0 record button\n23FA - \; unqualified # ⏺ E2.0 record button\n23CF - FE0F \; fully-qualified # ⏏️ E2.0 - eject button\n23CF \; unqualified - # ⏏ E2.0 eject button\n1F3A6 - \; fully-qualified # 🎦 E2.0 cinema\n1F505 - \; fully-qualified # 🔅 E2.0 dim button\n1F506 - \; fully-qualified # 🔆 E2.0 bright but - ton\n1F4F6 \; fully-qualified # - 📶 E2.0 antenna bars\n1F4F3 \; full - y-qualified # 📳 E2.0 vibration mode\n1F4F4 - \; fully-qualified # 📴 E2.0 mobile phone off\n\n# subg - roup: gender\n2640 FE0F \; fully-qualifie - d # ♀️ E4.0 female sign\n2640 - \; unqualified # ♀ E4.0 female sign\n2642 FE0F - \; fully-qualified # ♂️ E4.0 male sign\n2642 - \; unqualified # ♂ E4.0 male - sign\n\n# subgroup: other-symbol\n2695 FE0F - \; fully-qualified # ⚕️ E4.0 medical symbol\n2695 - \; unqualified # ⚕ E4.0 medical symbol\ - n267E FE0F \; fully-qualified # ♾ - ️ E11.0 infinity\n267E \; unqualif - ied # ♾ E11.0 infinity\n267B FE0F - \; fully-qualified # ♻️ E2.0 recycling symbol\n267B - \; unqualified # ♻ E2.0 recycling sym - bol\n269C FE0F \; fully-qualified # - ⚜️ E2.0 fleur-de-lis\n269C \; un - qualified # ⚜ E2.0 fleur-de-lis\n1F531 - \; fully-qualified # 🔱 E2.0 trident emblem\n1F4DB - \; fully-qualified # 📛 E2.0 name ba - dge\n1F530 \; fully-qualified # - 🔰 E2.0 Japanese symbol for beginner\n2B55 - \; fully-qualified # ⭕ E2.0 hollow red circle\n2705 - \; fully-qualified # ✅ E2.0 check mar - k button\n2611 FE0F \; fully-qualified - # ☑️ E2.0 check box with check\n2611 - \; unqualified # ☑ E2.0 check box with check\n2714 FE0F - \; fully-qualified # ✔️ E2.0 check - mark\n2714 \; unqualified # - ✔ E2.0 check mark\n2716 FE0F \; fully- - qualified # ✖️ E2.0 multiplication sign\n2716 - \; unqualified # ✖ E2.0 multiplication sign\n2 - 74C \; fully-qualified # ❌ E2. - 0 cross mark\n274E \; fully-qualifie - d # ❎ E2.0 cross mark button\n2795 - \; fully-qualified # ➕ E2.0 plus sign\n2796 - \; fully-qualified # ➖ E2.0 minus sign\n2797 - \; fully-qualified # ➗ E2.0 divisi - on sign\n27B0 \; fully-qualified - # ➰ E2.0 curly loop\n27BF \; full - y-qualified # ➿ E2.0 double curly loop\n303D FE0F - \; fully-qualified # 〽️ E2.0 part alternation mark\ - n303D \; unqualified # 〽 E - 2.0 part alternation mark\n2733 FE0F \; f - ully-qualified # ✳️ E2.0 eight-spoked asterisk\n2733 - \; unqualified # ✳ E2.0 eight-spoked as - terisk\n2734 FE0F \; fully-qualified - # ✴️ E2.0 eight-pointed star\n2734 - \; unqualified # ✴ E2.0 eight-pointed star\n2747 FE0F - \; fully-qualified # ❇️ E2.0 sparkle\n27 - 47 \; unqualified # ❇ E2.0 - sparkle\n203C FE0F \; fully-qualified - # ‼️ E2.0 double exclamation mark\n203C - \; unqualified # ‼ E2.0 double exclamation mark\n2049 - FE0F \; fully-qualified # ⁉️ E2.0 - exclamation question mark\n2049 \; - unqualified # ⁉ E2.0 exclamation question mark\n2753 - \; fully-qualified # ❓ E2.0 question mark - \n2754 \; fully-qualified # ❔ - E2.0 white question mark\n2755 \; fu - lly-qualified # ❕ E2.0 white exclamation mark\n2757 - \; fully-qualified # ❗ E2.0 exclamation mark\n - 3030 FE0F \; fully-qualified # 〰️ - E2.0 wavy dash\n3030 \; unqualified - # 〰 E2.0 wavy dash\n00A9 FE0F - \; fully-qualified # ©️ E2.0 copyright\n00A9 - \; unqualified # © E2.0 copyright\n00AE FE0F - \; fully-qualified # ®️ E2.0 registere - d\n00AE \; unqualified # ® - E2.0 registered\n2122 FE0F \; fully-quali - fied # ™️ E2.0 trade mark\n2122 - \; unqualified # ™ E2.0 trade mark\n\n# subgroup: keycap\n00 - 23 FE0F 20E3 \; fully-qualified # #️⃣ - E0.0 keycap: #\n0023 20E3 \; unqualified - # #⃣ E0.0 keycap: #\n002A FE0F 20E3 - \; fully-qualified # *️⃣ E0.0 keycap: *\n002A 20E3 - \; unqualified # *⃣ E0.0 keycap: *\n0030 FE0F - 20E3 \; fully-qualified # 0️⃣ E0.0 key - cap: 0\n0030 20E3 \; unqualified - # 0⃣ E0.0 keycap: 0\n0031 FE0F 20E3 \; fully - -qualified # 1️⃣ E0.0 keycap: 1\n0031 20E3 - \; unqualified # 1⃣ E0.0 keycap: 1\n0032 FE0F 20E3 - \; fully-qualified # 2️⃣ E0.0 keycap: 2\n - 0032 20E3 \; unqualified # 2⃣ E - 0.0 keycap: 2\n0033 FE0F 20E3 \; fully-qualifi - ed # 3️⃣ E0.0 keycap: 3\n0033 20E3 - \; unqualified # 3⃣ E0.0 keycap: 3\n0034 FE0F 20E3 - \; fully-qualified # 4️⃣ E0.0 keycap: 4\n0034 20E - 3 \; unqualified # 4⃣ E0.0 keyc - ap: 4\n0035 FE0F 20E3 \; fully-qualified # - 5️⃣ E0.0 keycap: 5\n0035 20E3 \; unq - ualified # 5⃣ E0.0 keycap: 5\n0036 FE0F 20E3 - \; fully-qualified # 6️⃣ E0.0 keycap: 6\n0036 20E3 - \; unqualified # 6⃣ E0.0 keycap: 6\n0 - 037 FE0F 20E3 \; fully-qualified # 7️⃣ - E0.0 keycap: 7\n0037 20E3 \; unqualified - # 7⃣ E0.0 keycap: 7\n0038 FE0F 20E3 - \; fully-qualified # 8️⃣ E0.0 keycap: 8\n0038 20E3 - \; unqualified # 8⃣ E0.0 keycap: 8\n0039 FE0F - 20E3 \; fully-qualified # 9️⃣ E0.0 ke - ycap: 9\n0039 20E3 \; unqualified - # 9⃣ E0.0 keycap: 9\n1F51F \; full - y-qualified # 🔟 E2.0 keycap: 10\n\n# subgroup: alphanum\n1F520 - \; fully-qualified # 🔠 E2.0 input - latin uppercase\n1F521 \; fully-quali - fied # 🔡 E2.0 input latin lowercase\n1F522 - \; fully-qualified # 🔢 E2.0 input numbers\n1F523 - \; fully-qualified # 🔣 E2.0 input s - ymbols\n1F524 \; fully-qualified - # 🔤 E2.0 input latin letters\n1F170 FE0F - \; fully-qualified # 🅰️ E2.0 A button (blood type)\n1F170 - \; unqualified # 🅰 E2.0 A butto - n (blood type)\n1F18E \; fully-qualif - ied # 🆎 E2.0 AB button (blood type)\n1F171 FE0F - \; fully-qualified # 🅱️ E2.0 B button (blood type)\n - 1F171 \; unqualified # 🅱 E - 2.0 B button (blood type)\n1F191 \; f - ully-qualified # 🆑 E2.0 CL button\n1F192 - \; fully-qualified # 🆒 E2.0 COOL button\n1F193 - \; fully-qualified # 🆓 E2.0 FREE button - \n2139 FE0F \; fully-qualified # ℹ - ️ E2.0 information\n2139 \; unqual - ified # ℹ E2.0 information\n1F194 - \; fully-qualified # 🆔 E2.0 ID button\n24C2 FE0F - \; fully-qualified # Ⓜ️ E2.0 circled M\n24C2 - \; unqualified # Ⓜ E2.0 c - ircled M\n1F195 \; fully-qualified - # 🆕 E2.0 NEW button\n1F196 \; fu - lly-qualified # 🆖 E2.0 NG button\n1F17E FE0F - \; fully-qualified # 🅾️ E2.0 O button (blood type)\n1F1 - 7E \; unqualified # 🅾 E2.0 - O button (blood type)\n1F197 \; full - y-qualified # 🆗 E2.0 OK button\n1F17F FE0F - \; fully-qualified # 🅿️ E2.0 P button\n1F17F - \; unqualified # 🅿 E2.0 P button\n1F19 - 8 \; fully-qualified # 🆘 E2.0 - SOS button\n1F199 \; fully-qualified - # 🆙 E2.0 UP! button\n1F19A \; - fully-qualified # 🆚 E2.0 VS button\n1F201 - \; fully-qualified # 🈁 E2.0 Japanese “here” button\ - n1F202 FE0F \; fully-qualified # 🈂 - ️ E2.0 Japanese “service charge” button\n1F202 - \; unqualified # 🈂 E2.0 Japanese “service cha - rge” button\n1F237 FE0F \; fully-qualifi - ed # 🈷️ E2.0 Japanese “monthly amount” button\n1F237 - \; unqualified # �� E2.0 Japanese - “monthly amount” button\n1F236 \ - ; fully-qualified # 🈶 E2.0 Japanese “not free of charge” button - \n1F22F \; fully-qualified # 🈯 - E2.0 Japanese “reserved” button\n1F250 - \; fully-qualified # 🉐 E2.0 Japanese “bargain” button\n1 - F239 \; fully-qualified # 🈹 E2 - .0 Japanese “discount” button\n1F21A - \; fully-qualified # 🈚 E2.0 Japanese “free of charge” butto - n\n1F232 \; fully-qualified # - 🈲 E2.0 Japanese “prohibited” button\n1F251 - \; fully-qualified # 🉑 E2.0 Japanese “acceptable” - button\n1F238 \; fully-qualified - # 🈸 E2.0 Japanese “application” button\n1F234 - \; fully-qualified # 🈴 E2.0 Japanese “passing gra - de” button\n1F233 \; fully-qualifie - d # 🈳 E2.0 Japanese “vacancy” button\n3297 FE0F - \; fully-qualified # ㊗️ E2.0 Japanese “congrat - ulations” button\n3297 \; unqualif - ied # ㊗ E2.0 Japanese “congratulations” button\n3299 FE0F - \; fully-qualified # ㊙️ E2.0 Japane - se “secret” button\n3299 \; unqu - alified # ㊙ E2.0 Japanese “secret” button\n1F23A - \; fully-qualified # 🈺 E2.0 Japanese “o - pen for business” button\n1F235 \; - fully-qualified # 🈵 E2.0 Japanese “no vacancy” button\n\n# subg - roup: geometric\n1F534 \; fully-quali - fied # 🔴 E2.0 red circle\n1F7E0 - \; fully-qualified # 🟠 E12.1 orange circle\n1F7E1 - \; fully-qualified # 🟡 E12.1 yellow circle\n1 - F7E2 \; fully-qualified # 🟢 E1 - 2.1 green circle\n1F535 \; fully-qual - ified # 🔵 E2.0 blue circle\n1F7E3 - \; fully-qualified # 🟣 E12.1 purple circle\n1F7E4 - \; fully-qualified # 🟤 E12.1 brown circle\n - 26AB \; fully-qualified # ⚫ E2 - .0 black circle\n26AA \; fully-quali - fied # ⚪ E2.0 white circle\n1F7E5 - \; fully-qualified # 🟥 E12.1 red square\n1F7E7 - \; fully-qualified # 🟧 E12.1 orange square\n1F7 - E8 \; fully-qualified # 🟨 E12. - 1 yellow square\n1F7E9 \; fully-quali - fied # 🟩 E12.1 green square\n1F7E6 - \; fully-qualified # 🟦 E12.1 blue square\n1F7EA - \; fully-qualified # 🟪 E12.1 purple square\n - 1F7EB \; fully-qualified # 🟫 E - 12.1 brown square\n2B1B \; fully-qua - lified # ⬛ E2.0 black large square\n2B1C - \; fully-qualified # ⬜ E2.0 white large square\n25FC FE0F - \; fully-qualified # ◼️ E2.0 bla - ck medium square\n25FC \; unqualifie - d # ◼ E2.0 black medium square\n25FB FE0F - \; fully-qualified # ◻️ E2.0 white medium square\n25FB - \; unqualified # ◻ E2.0 wh - ite medium square\n25FE \; fully-qua - lified # ◾ E2.0 black medium-small square\n25FD - \; fully-qualified # ◽ E2.0 white medium-small squ - are\n25AA FE0F \; fully-qualified # - ▪️ E2.0 black small square\n25AA - \; unqualified # ▪ E2.0 black small square\n25AB FE0F - \; fully-qualified # ▫️ E2.0 white small s - quare\n25AB \; unqualified # - ▫ E2.0 white small square\n1F536 \ - ; fully-qualified # 🔶 E2.0 large orange diamond\n1F537 - \; fully-qualified # 🔷 E2.0 large blue dia - mond\n1F538 \; fully-qualified # - �� E2.0 small orange diamond\n1F539 - \; fully-qualified # 🔹 E2.0 small blue diamond\n1F53A - \; fully-qualified # 🔺 E2.0 red triangle - pointed up\n1F53B \; fully-qualified - # 🔻 E2.0 red triangle pointed down\n1F4A0 - \; fully-qualified # 💠 E2.0 diamond with a dot\n1F518 - \; fully-qualified # 🔘 E2.0 ra - dio button\n1F533 \; fully-qualified - # 🔳 E2.0 white square button\n1F532 - \; fully-qualified # 🔲 E2.0 black square button\n\n# Symbols - subtotal: 297\n# Symbols subtotal: 297 w/o modifiers\n\n# group: Flags\n - \n# subgroup: flag\n1F3C1 \; fully-qu - alified # 🏁 E2.0 chequered flag\n1F6A9 - \; fully-qualified # 🚩 E2.0 triangular flag\n1F38C - \; fully-qualified # 🎌 E2.0 crossed f - lags\n1F3F4 \; fully-qualified # - 🏴 E2.0 black flag\n1F3F3 FE0F \; fully- - qualified # 🏳️ E2.0 white flag\n1F3F3 - \; unqualified # 🏳 E2.0 white flag\n1F3F3 FE0F 200D 1 - F308 \; fully-qualified # ��️‍🌈 E4.0 r - ainbow flag\n1F3F3 200D 1F308 \; unqualified - # 🏳‍🌈 E4.0 rainbow flag\n1F3F4 200D 2620 FE0F - \; fully-qualified # 🏴‍☠️ E11.0 pirate flag\n1F3F4 200 - D 2620 \; minimally-qualified # 🏴‍☠ E11. - 0 pirate flag\n\n# subgroup: country-flag\n1F1E6 1F1E8 - \; fully-qualified # 🇦🇨 E2.0 flag: Ascension Island\ - n1F1E6 1F1E9 \; fully-qualified # 🇦 - 🇩 E2.0 flag: Andorra\n1F1E6 1F1EA \; ful - ly-qualified # 🇦🇪 E2.0 flag: United Arab Emirates\n1F1E6 1F1EB - \; fully-qualified # 🇦🇫 E2.0 flag: - Afghanistan\n1F1E6 1F1EC \; fully-qualifie - d # 🇦🇬 E2.0 flag: Antigua & Barbuda\n1F1E6 1F1EE - \; fully-qualified # 🇦🇮 E2.0 flag: Anguilla\n1F1 - E6 1F1F1 \; fully-qualified # 🇦🇱 - E2.0 flag: Albania\n1F1E6 1F1F2 \; fully-qu - alified # 🇦🇲 E2.0 flag: Armenia\n1F1E6 1F1F4 - \; fully-qualified # 🇦🇴 E2.0 flag: Angola\n1F1E6 1F1 - F6 \; fully-qualified # 🇦�� E2.0 - flag: Antarctica\n1F1E6 1F1F7 \; fully-qua - lified # 🇦🇷 E2.0 flag: Argentina\n1F1E6 1F1F8 - \; fully-qualified # 🇦🇸 E2.0 flag: American Samoa\n - 1F1E6 1F1F9 \; fully-qualified # 🇦 - 🇹 E2.0 flag: Austria\n1F1E6 1F1FA \; ful - ly-qualified # 🇦🇺 E2.0 flag: Australia\n1F1E6 1F1FC - \; fully-qualified # 🇦🇼 E2.0 flag: Aruba\n1F1 - E6 1F1FD \; fully-qualified # 🇦🇽 - E2.0 flag: Åland Islands\n1F1E6 1F1FF \; f - ully-qualified # 🇦🇿 E2.0 flag: Azerbaijan\n1F1E7 1F1E6 - \; fully-qualified # 🇧🇦 E2.0 flag: Bosnia - & Herzegovina\n1F1E7 1F1E7 \; fully-qualifi - ed # 🇧🇧 E2.0 flag: Barbados\n1F1E7 1F1E9 - \; fully-qualified # 🇧🇩 E2.0 flag: Bangladesh\n1F1E7 1F1 - EA \; fully-qualified # 🇧🇪 E2.0 f - lag: Belgium\n1F1E7 1F1EB \; fully-qualifie - d # 🇧🇫 E2.0 flag: Burkina Faso\n1F1E7 1F1EC - \; fully-qualified # 🇧🇬 E2.0 flag: Bulgaria\n1F1E7 1F - 1ED \; fully-qualified # 🇧🇭 E2.0 - flag: Bahrain\n1F1E7 1F1EE \; fully-qualifi - ed # 🇧🇮 E2.0 flag: Burundi\n1F1E7 1F1EF - \; fully-qualified # 🇧🇯 E2.0 flag: Benin\n1F1E7 1F1F1 - \; fully-qualified # 🇧🇱 E2.0 flag: S - t. Barthélemy\n1F1E7 1F1F2 \; fully-qualif - ied # 🇧🇲 E2.0 flag: Bermuda\n1F1E7 1F1F3 - \; fully-qualified # 🇧🇳 E2.0 flag: Brunei\n1F1E7 1F1F4 - \; fully-qualified # 🇧🇴 E2.0 flag: - Bolivia\n1F1E7 1F1F6 \; fully-qualified - # 🇧🇶 E2.0 flag: Caribbean Netherlands\n1F1E7 1F1F7 - \; fully-qualified # 🇧🇷 E2.0 flag: Brazil\n1F1E7 - 1F1F8 \; fully-qualified # 🇧🇸 E2 - .0 flag: Bahamas\n1F1E7 1F1F9 \; fully-qual - ified # 🇧🇹 E2.0 flag: Bhutan\n1F1E7 1F1FB - \; fully-qualified # 🇧🇻 E2.0 flag: Bouvet Island\n1F1E7 - 1F1FC \; fully-qualified # 🇧🇼 E2 - .0 flag: Botswana\n1F1E7 1F1FE \; fully-qua - lified # 🇧🇾 E2.0 flag: Belarus\n1F1E7 1F1FF - \; fully-qualified # 🇧🇿 E2.0 flag: Belize\n1F1E8 1F1E - 6 \; fully-qualified # 🇨🇦 E2.0 fl - ag: Canada\n1F1E8 1F1E8 \; fully-qualified - # 🇨🇨 E2.0 flag: Cocos (Keeling) Islands\n1F1E8 1F1E9 - \; fully-qualified # 🇨🇩 E2.0 flag: Congo - K - inshasa\n1F1E8 1F1EB \; fully-qualified - # 🇨🇫 E2.0 flag: Central African Republic\n1F1E8 1F1EC - \; fully-qualified # 🇨🇬 E2.0 flag: Congo - Bra - zzaville\n1F1E8 1F1ED \; fully-qualified - # 🇨🇭 E2.0 flag: Switzerland\n1F1E8 1F1EE - \; fully-qualified # 🇨🇮 E2.0 flag: Côte d’Ivoire\n1F1E8 - 1F1F0 \; fully-qualified # 🇨🇰 E2 - .0 flag: Cook Islands\n1F1E8 1F1F1 \; fully - -qualified # 🇨🇱 E2.0 flag: Chile\n1F1E8 1F1F2 - \; fully-qualified # 🇨🇲 E2.0 flag: Cameroon\n1F1E8 - 1F1F3 \; fully-qualified # 🇨🇳 E2. - 0 flag: China\n1F1E8 1F1F4 \; fully-qualifi - ed # 🇨🇴 E2.0 flag: Colombia\n1F1E8 1F1F5 - \; fully-qualified # 🇨🇵 E2.0 flag: Clipperton Island\n1F - 1E8 1F1F7 \; fully-qualified # 🇨🇷 - E2.0 flag: Costa Rica\n1F1E8 1F1FA \; full - y-qualified # 🇨🇺 E2.0 flag: Cuba\n1F1E8 1F1FB - \; fully-qualified # 🇨🇻 E2.0 flag: Cape Verde\n1F1E - 8 1F1FC \; fully-qualified # 🇨🇼 E - 2.0 flag: Curaçao\n1F1E8 1F1FD \; fully-qu - alified # 🇨🇽 E2.0 flag: Christmas Island\n1F1E8 1F1FE - \; fully-qualified # 🇨🇾 E2.0 flag: Cyprus\n - 1F1E8 1F1FF \; fully-qualified # 🇨 - 🇿 E2.0 flag: Czechia\n1F1E9 1F1EA \; ful - ly-qualified # 🇩🇪 E2.0 flag: Germany\n1F1E9 1F1EC - \; fully-qualified # 🇩🇬 E2.0 flag: Diego Garcia - \n1F1E9 1F1EF \; fully-qualified # 🇩 - 🇯 E2.0 flag: Djibouti\n1F1E9 1F1F0 \; fu - lly-qualified # 🇩🇰 E2.0 flag: Denmark\n1F1E9 1F1F2 - \; fully-qualified # 🇩🇲 E2.0 flag: Dominica\n1 - F1E9 1F1F4 \; fully-qualified # 🇩 - 🇴 E2.0 flag: Dominican Republic\n1F1E9 1F1FF - \; fully-qualified # 🇩🇿 E2.0 flag: Algeria\n1F1EA 1F1E6 - \; fully-qualified # 🇪🇦 E2.0 flag: C - euta & Melilla\n1F1EA 1F1E8 \; fully-qualif - ied # 🇪🇨 E2.0 flag: Ecuador\n1F1EA 1F1EA - \; fully-qualified # 🇪🇪 E2.0 flag: Estonia\n1F1EA 1F1EC - \; fully-qualified # 🇪🇬 E2.0 flag - : Egypt\n1F1EA 1F1ED \; fully-qualified - # 🇪🇭 E2.0 flag: Western Sahara\n1F1EA 1F1F7 - \; fully-qualified # 🇪🇷 E2.0 flag: Eritrea\n1F1EA 1F1F8 - \; fully-qualified # 🇪🇸 E2.0 flag - : Spain\n1F1EA 1F1F9 \; fully-qualified - # 🇪🇹 E2.0 flag: Ethiopia\n1F1EA 1F1FA - \; fully-qualified # 🇪🇺 E2.0 flag: European Union\n1F1EB 1F1EE - \; fully-qualified # 🇫🇮 E2.0 fla - g: Finland\n1F1EB 1F1EF \; fully-qualified - # 🇫🇯 E2.0 flag: Fiji\n1F1EB 1F1F0 - \; fully-qualified # 🇫🇰 E2.0 flag: Falkland Islands\n1F1EB 1F1F - 2 \; fully-qualified # 🇫🇲 E2.0 fl - ag: Micronesia\n1F1EB 1F1F4 \; fully-qualif - ied # 🇫🇴 E2.0 flag: Faroe Islands\n1F1EB 1F1F7 - \; fully-qualified # 🇫🇷 E2.0 flag: France\n1F1EC 1 - F1E6 \; fully-qualified # 🇬🇦 E2.0 - flag: Gabon\n1F1EC 1F1E7 \; fully-qualifie - d # 🇬🇧 E2.0 flag: United Kingdom\n1F1EC 1F1E9 - \; fully-qualified # 🇬🇩 E2.0 flag: Grenada\n1F1EC 1 - F1EA \; fully-qualified # 🇬🇪 E2.0 - flag: Georgia\n1F1EC 1F1EB \; fully-qualif - ied # 🇬🇫 E2.0 flag: French Guiana\n1F1EC 1F1EC - \; fully-qualified # 🇬🇬 E2.0 flag: Guernsey\n1F1EC - 1F1ED \; fully-qualified # 🇬🇭 E2 - .0 flag: Ghana\n1F1EC 1F1EE \; fully-qualif - ied # 🇬🇮 E2.0 flag: Gibraltar\n1F1EC 1F1F1 - \; fully-qualified # 🇬🇱 E2.0 flag: Greenland\n1F1EC 1F - 1F2 \; fully-qualified # 🇬🇲 E2.0 - flag: Gambia\n1F1EC 1F1F3 \; fully-qualifie - d # 🇬🇳 E2.0 flag: Guinea\n1F1EC 1F1F5 - \; fully-qualified # 🇬🇵 E2.0 flag: Guadeloupe\n1F1EC 1F1F6 - \; fully-qualified # 🇬🇶 E2.0 flag - : Equatorial Guinea\n1F1EC 1F1F7 \; fully-q - ualified # 🇬🇷 E2.0 flag: Greece\n1F1EC 1F1F8 - \; fully-qualified # 🇬🇸 E2.0 flag: South Georgia & S - outh Sandwich Islands\n1F1EC 1F1F9 \; fully - -qualified # 🇬🇹 E2.0 flag: Guatemala\n1F1EC 1F1FA - \; fully-qualified # 🇬🇺 E2.0 flag: Guam\n1F1EC - 1F1FC \; fully-qualified # 🇬🇼 E2. - 0 flag: Guinea-Bissau\n1F1EC 1F1FE \; fully - -qualified # 🇬🇾 E2.0 flag: Guyana\n1F1ED 1F1F0 - \; fully-qualified # 🇭🇰 E2.0 flag: Hong Kong SAR C - hina\n1F1ED 1F1F2 \; fully-qualified # - 🇭🇲 E2.0 flag: Heard & McDonald Islands\n1F1ED 1F1F3 - \; fully-qualified # 🇭🇳 E2.0 flag: Honduras\n1F1E - D 1F1F7 \; fully-qualified # 🇭🇷 E - 2.0 flag: Croatia\n1F1ED 1F1F9 \; fully-qua - lified # 🇭🇹 E2.0 flag: Haiti\n1F1ED 1F1FA - \; fully-qualified # 🇭🇺 E2.0 flag: Hungary\n1F1EE 1F1E8 - \; fully-qualified # 🇮🇨 E2.0 fla - g: Canary Islands\n1F1EE 1F1E9 \; fully-qua - lified # 🇮🇩 E2.0 flag: Indonesia\n1F1EE 1F1EA - \; fully-qualified # 🇮🇪 E2.0 flag: Ireland\n1F1EE 1 - F1F1 \; fully-qualified # 🇮🇱 E2.0 - flag: Israel\n1F1EE 1F1F2 \; fully-qualifi - ed # 🇮🇲 E2.0 flag: Isle of Man\n1F1EE 1F1F3 - \; fully-qualified # 🇮🇳 E2.0 flag: India\n1F1EE 1F1F4 - \; fully-qualified # 🇮🇴 E2.0 fla - g: British Indian Ocean Territory\n1F1EE 1F1F6 - \; fully-qualified # 🇮🇶 E2.0 flag: Iraq\n1F1EE 1F1F7 - \; fully-qualified # 🇮🇷 E2.0 flag: Iran\ - n1F1EE 1F1F8 \; fully-qualified # 🇮 - 🇸 E2.0 flag: Iceland\n1F1EE 1F1F9 \; ful - ly-qualified # 🇮🇹 E2.0 flag: Italy\n1F1EF 1F1EA - \; fully-qualified # 🇯🇪 E2.0 flag: Jersey\n1F1EF - 1F1F2 \; fully-qualified # 🇯🇲 E2. - 0 flag: Jamaica\n1F1EF 1F1F4 \; fully-quali - fied # 🇯🇴 E2.0 flag: Jordan\n1F1EF 1F1F5 - \; fully-qualified # 🇯🇵 E2.0 flag: Japan\n1F1F0 1F1EA - \; fully-qualified # 🇰🇪 E2.0 flag: - Kenya\n1F1F0 1F1EC \; fully-qualified # - 🇰🇬 E2.0 flag: Kyrgyzstan\n1F1F0 1F1ED - \; fully-qualified # 🇰🇭 E2.0 flag: Cambodia\n1F1F0 1F1EE - \; fully-qualified # 🇰🇮 E2.0 flag: Kir - ibati\n1F1F0 1F1F2 \; fully-qualified # - 🇰🇲 E2.0 flag: Comoros\n1F1F0 1F1F3 \ - ; fully-qualified # 🇰🇳 E2.0 flag: St. Kitts & Nevis\n1F1F0 1F1F5 - \; fully-qualified # 🇰🇵 E2.0 fla - g: North Korea\n1F1F0 1F1F7 \; fully-qualif - ied # 🇰🇷 E2.0 flag: South Korea\n1F1F0 1F1FC - \; fully-qualified # 🇰�� E2.0 flag: Kuwait\n1F1F0 1 - F1FE \; fully-qualified # 🇰🇾 E2.0 - flag: Cayman Islands\n1F1F0 1F1FF \; fully - -qualified # 🇰🇿 E2.0 flag: Kazakhstan\n1F1F1 1F1E6 - \; fully-qualified # 🇱🇦 E2.0 flag: Laos\n1F1F1 - 1F1E7 \; fully-qualified # 🇱🇧 E2 - .0 flag: Lebanon\n1F1F1 1F1E8 \; fully-qual - ified # 🇱🇨 E2.0 flag: St. Lucia\n1F1F1 1F1EE - \; fully-qualified # 🇱🇮 E2.0 flag: Liechtenstein\n1F - 1F1 1F1F0 \; fully-qualified # 🇱🇰 - E2.0 flag: Sri Lanka\n1F1F1 1F1F7 \; fully - -qualified # 🇱🇷 E2.0 flag: Liberia\n1F1F1 1F1F8 - \; fully-qualified # 🇱🇸 E2.0 flag: Lesotho\n1F1F1 - 1F1F9 \; fully-qualified # 🇱🇹 E2 - .0 flag: Lithuania\n1F1F1 1F1FA \; fully-qu - alified # 🇱🇺 E2.0 flag: Luxembourg\n1F1F1 1F1FB - \; fully-qualified # 🇱🇻 E2.0 flag: Latvia\n1F1F1 - 1F1FE \; fully-qualified # 🇱🇾 E2. - 0 flag: Libya\n1F1F2 1F1E6 \; fully-qualifi - ed # 🇲🇦 E2.0 flag: Morocco\n1F1F2 1F1E8 - \; fully-qualified # 🇲🇨 E2.0 flag: Monaco\n1F1F2 1F1E9 - \; fully-qualified # 🇲🇩 E2.0 flag: - Moldova\n1F1F2 1F1EA \; fully-qualified - # 🇲🇪 E2.0 flag: Montenegro\n1F1F2 1F1EB - \; fully-qualified # 🇲🇫 E2.0 flag: St. Martin\n1F1F2 1F1EC - \; fully-qualified # 🇲🇬 E2.0 flag: - Madagascar\n1F1F2 1F1ED \; fully-qualified - # 🇲🇭 E2.0 flag: Marshall Islands\n1F1F2 1F1F0 - \; fully-qualified # 🇲🇰 E2.0 flag: North Macedonia - \n1F1F2 1F1F1 \; fully-qualified # 🇲 - 🇱 E2.0 flag: Mali\n1F1F2 1F1F2 \; fully- - qualified # 🇲🇲 E2.0 flag: Myanmar (Burma)\n1F1F2 1F1F3 - \; fully-qualified # 🇲🇳 E2.0 flag: Mongoli - a\n1F1F2 1F1F4 \; fully-qualified # - 🇲🇴 E2.0 flag: Macao SAR China\n1F1F2 1F1F5 - \; fully-qualified # 🇲�� E2.0 flag: Northern Mariana Isla - nds\n1F1F2 1F1F6 \; fully-qualified # - 🇲🇶 E2.0 flag: Martinique\n1F1F2 1F1F7 - \; fully-qualified # 🇲🇷 E2.0 flag: Mauritania\n1F1F2 1F1F8 - \; fully-qualified # ��🇸 E2.0 flag: - Montserrat\n1F1F2 1F1F9 \; fully-qualified - # 🇲🇹 E2.0 flag: Malta\n1F1F2 1F1FA - \; fully-qualified # 🇲🇺 E2.0 flag: Mauritius\n1F1F2 1F1FB - \; fully-qualified # 🇲🇻 E2.0 flag: Ma - ldives\n1F1F2 1F1FC \; fully-qualified - # 🇲🇼 E2.0 flag: Malawi\n1F1F2 1F1FD \ - ; fully-qualified # 🇲🇽 E2.0 flag: Mexico\n1F1F2 1F1FE - \; fully-qualified # 🇲🇾 E2.0 flag: Malaysia - \n1F1F2 1F1FF \; fully-qualified # 🇲 - 🇿 E2.0 flag: Mozambique\n1F1F3 1F1E6 \; - fully-qualified # ��🇦 E2.0 flag: Namibia\n1F1F3 1F1E8 - \; fully-qualified # 🇳🇨 E2.0 flag: New Cal - edonia\n1F1F3 1F1EA \; fully-qualified - # 🇳🇪 E2.0 flag: Niger\n1F1F3 1F1EB \; - fully-qualified # 🇳🇫 E2.0 flag: Norfolk Island\n1F1F3 1F1EC - \; fully-qualified # 🇳🇬 E2.0 flag: N - igeria\n1F1F3 1F1EE \; fully-qualified - # 🇳🇮 E2.0 flag: Nicaragua\n1F1F3 1F1F1 - \; fully-qualified # 🇳🇱 E2.0 flag: Netherlands\n1F1F3 1F1F4 - \; fully-qualified # 🇳🇴 E2.0 flag: - Norway\n1F1F3 1F1F5 \; fully-qualified - # 🇳🇵 E2.0 flag: Nepal\n1F1F3 1F1F7 \; - fully-qualified # 🇳🇷 E2.0 flag: Nauru\n1F1F3 1F1FA - \; fully-qualified # 🇳🇺 E2.0 flag: Niue\n1F1F - 3 1F1FF \; fully-qualified # 🇳🇿 E - 2.0 flag: New Zealand\n1F1F4 1F1F2 \; fully - -qualified # 🇴🇲 E2.0 flag: Oman\n1F1F5 1F1E6 - \; fully-qualified # 🇵🇦 E2.0 flag: Panama\n1F1F5 1F1 - EA \; fully-qualified # 🇵🇪 E2.0 f - lag: Peru\n1F1F5 1F1EB \; fully-qualified - # 🇵🇫 E2.0 flag: French Polynesia\n1F1F5 1F1EC - \; fully-qualified # 🇵🇬 E2.0 flag: Papua New Guinea\ - n1F1F5 1F1ED \; fully-qualified # 🇵 - 🇭 E2.0 flag: Philippines\n1F1F5 1F1F0 \; - fully-qualified # 🇵🇰 E2.0 flag: Pakistan\n1F1F5 1F1F1 - \; fully-qualified # 🇵🇱 E2.0 flag: Poland\ - n1F1F5 1F1F2 \; fully-qualified # 🇵 - 🇲 E2.0 flag: St. Pierre & Miquelon\n1F1F5 1F1F3 - \; fully-qualified # 🇵🇳 E2.0 flag: Pitcairn Islands\n1F1 - F5 1F1F7 \; fully-qualified # 🇵🇷 - E2.0 flag: Puerto Rico\n1F1F5 1F1F8 \; full - y-qualified # 🇵🇸 E2.0 flag: Palestinian Territories\n1F1F5 1F1F9 - \; fully-qualified # 🇵🇹 E2.0 fla - g: Portugal\n1F1F5 1F1FC \; fully-qualified - # 🇵🇼 E2.0 flag: Palau\n1F1F5 1F1FE - \; fully-qualified # 🇵🇾 E2.0 flag: Paraguay\n1F1F6 1F1E6 - \; fully-qualified # 🇶🇦 E2.0 flag: Qa - tar\n1F1F7 1F1EA \; fully-qualified # - 🇷🇪 E2.0 flag: Réunion\n1F1F7 1F1F4 \ - ; fully-qualified # 🇷🇴 E2.0 flag: Romania\n1F1F7 1F1F8 - \; fully-qualified # 🇷🇸 E2.0 flag: Serbia\ - n1F1F7 1F1FA \; fully-qualified # 🇷 - 🇺 E2.0 flag: Russia\n1F1F7 1F1FC \; full - y-qualified # 🇷🇼 E2.0 flag: Rwanda\n1F1F8 1F1E6 - \; fully-qualified # 🇸🇦 E2.0 flag: Saudi Arabia\n - 1F1F8 1F1E7 \; fully-qualified # 🇸 - 🇧 E2.0 flag: Solomon Islands\n1F1F8 1F1E8 - \; fully-qualified # 🇸🇨 E2.0 flag: Seychelles\n1F1F8 1F1E9 - \; fully-qualified # 🇸🇩 E2.0 flag: S - udan\n1F1F8 1F1EA \; fully-qualified # - 🇸🇪 E2.0 flag: Sweden\n1F1F8 1F1EC \; - fully-qualified # 🇸🇬 E2.0 flag: Singapore\n1F1F8 1F1ED - \; fully-qualified # 🇸🇭 E2.0 flag: St. Hel - ena\n1F1F8 1F1EE \; fully-qualified # - 🇸🇮 E2.0 flag: Slovenia\n1F1F8 1F1EF \ - ; fully-qualified # 🇸�� E2.0 flag: Svalbard & Jan Mayen\n1F1F8 - 1F1F0 \; fully-qualified # 🇸🇰 E2. - 0 flag: Slovakia\n1F1F8 1F1F1 \; fully-qual - ified # 🇸🇱 E2.0 flag: Sierra Leone\n1F1F8 1F1F2 - \; fully-qualified # 🇸🇲 E2.0 flag: San Marino\n1F - 1F8 1F1F3 \; fully-qualified # 🇸🇳 - E2.0 flag: Senegal\n1F1F8 1F1F4 \; fully-q - ualified # 🇸🇴 E2.0 flag: Somalia\n1F1F8 1F1F7 - \; fully-qualified # 🇸🇷 E2.0 flag: Suriname\n1F1F8 - 1F1F8 \; fully-qualified # 🇸🇸 E2. - 0 flag: South Sudan\n1F1F8 1F1F9 \; fully-q - ualified # 🇸🇹 E2.0 flag: São Tomé & Príncipe\n1F1F8 1F1FB - \; fully-qualified # 🇸🇻 E2.0 flag: E - l Salvador\n1F1F8 1F1FD \; fully-qualified - # 🇸🇽 E2.0 flag: Sint Maarten\n1F1F8 1F1FE - \; fully-qualified # 🇸🇾 E2.0 flag: Syria\n1F1F8 1F1FF - \; fully-qualified # 🇸🇿 E2.0 flag: - Eswatini\n1F1F9 1F1E6 \; fully-qualified - # 🇹🇦 E2.0 flag: Tristan da Cunha\n1F1F9 1F1E8 - \; fully-qualified # 🇹🇨 E2.0 flag: Turks & Caicos Is - lands\n1F1F9 1F1E9 \; fully-qualified # - 🇹🇩 E2.0 flag: Chad\n1F1F9 1F1EB \; f - ully-qualified # 🇹🇫 E2.0 flag: French Southern Territories\n1F1F - 9 1F1EC \; fully-qualified # 🇹🇬 E - 2.0 flag: Togo\n1F1F9 1F1ED \; fully-qualif - ied # 🇹🇭 E2.0 flag: Thailand\n1F1F9 1F1EF - \; fully-qualified # 🇹🇯 E2.0 flag: Tajikistan\n1F1F9 1F - 1F0 \; fully-qualified # 🇹🇰 E2.0 - flag: Tokelau\n1F1F9 1F1F1 \; fully-qualifi - ed # 🇹🇱 E2.0 flag: Timor-Leste\n1F1F9 1F1F2 - \; fully-qualified # 🇹🇲 E2.0 flag: Turkmenistan\n1F1F - 9 1F1F3 \; fully-qualified # 🇹🇳 E - 2.0 flag: Tunisia\n1F1F9 1F1F4 \; fully-qua - lified # 🇹🇴 E2.0 flag: Tonga\n1F1F9 1F1F7 - \; fully-qualified # 🇹🇷 E2.0 flag: Turkey\n1F1F9 1F1F9 - \; fully-qualified # 🇹🇹 E2.0 flag - : Trinidad & Tobago\n1F1F9 1F1FB \; fully-q - ualified # 🇹🇻 E2.0 flag: Tuvalu\n1F1F9 1F1FC - \; fully-qualified # 🇹🇼 E2.0 flag: Taiwan\n1F1F9 1F1 - FF \; fully-qualified # 🇹🇿 E2.0 f - lag: Tanzania\n1F1FA 1F1E6 \; fully-qualifi - ed # 🇺🇦 E2.0 flag: Ukraine\n1F1FA 1F1EC - \; fully-qualified # 🇺🇬 E2.0 flag: Uganda\n1F1FA 1F1F2 - \; fully-qualified # 🇺🇲 E2.0 flag: - U.S. Outlying Islands\n1F1FA 1F1F3 \; fully - -qualified # 🇺🇳 E4.0 flag: United Nations\n1F1FA 1F1F8 - \; fully-qualified # 🇺🇸 E2.0 flag: United - States\n1F1FA 1F1FE \; fully-qualified - # 🇺🇾 E2.0 flag: Uruguay\n1F1FA 1F1FF - \; fully-qualified # 🇺🇿 E2.0 flag: Uzbekistan\n1F1FB 1F1E6 - \; fully-qualified # 🇻🇦 E2.0 flag: Vat - ican City\n1F1FB 1F1E8 \; fully-qualified - # 🇻🇨 E2.0 flag: St. Vincent & Grenadines\n1F1FB 1F1EA - \; fully-qualified # 🇻🇪 E2.0 flag: Venezuela - \n1F1FB 1F1EC \; fully-qualified # 🇻 - 🇬 E2.0 flag: British Virgin Islands\n1F1FB 1F1EE - \; fully-qualified # 🇻🇮 E2.0 flag: U.S. Virgin Islands\ - n1F1FB 1F1F3 \; fully-qualified # 🇻 - 🇳 E2.0 flag: Vietnam\n1F1FB 1F1FA \; ful - ly-qualified # 🇻🇺 E2.0 flag: Vanuatu\n1F1FC 1F1EB - \; fully-qualified # 🇼🇫 E2.0 flag: Wallis & Fut - una\n1F1FC 1F1F8 \; fully-qualified # - 🇼🇸 E2.0 flag: Samoa\n1F1FD 1F1F0 \; f - ully-qualified # 🇽🇰 E2.0 flag: Kosovo\n1F1FE 1F1EA - \; fully-qualified # 🇾🇪 E2.0 flag: Yemen\n1F1F - E 1F1F9 \; fully-qualified # 🇾🇹 E - 2.0 flag: Mayotte\n1F1FF 1F1E6 \; fully-qua - lified # 🇿🇦 E2.0 flag: South Africa\n1F1FF 1F1F2 - \; fully-qualified # 🇿🇲 E2.0 flag: Zambia\n1F1FF - 1F1FC \; fully-qualified # 🇿🇼 E2 - .0 flag: Zimbabwe\n\n# subgroup: subdivision-flag\n1F3F4 E0067 E0062 E0065 - E006E E0067 E007F \; fully-qualified # 🏴󠁧󠁢󠁥󠁮󠁧󠁿 - E5.0 flag: England\n1F3F4 E0067 E0062 E0073 E0063 E0074 E007F \; fully-qu - alified # 🏴󠁧󠁢󠁳󠁣󠁴󠁿 E5.0 flag: Scotland\n1F3F4 E006 - 7 E0062 E0077 E006C E0073 E007F \; fully-qualified # 🏴󠁧󠁢󠁷 - 󠁬󠁳󠁿 E5.0 flag: Wales\n\n# Flags subtotal: 271\n# Flags subtotal: - 271 w/o modifiers\n\n# Status Counts\n# fully-qualified : 3178\n# minima - lly-qualified : 589\n# unqualified : 246\n# component : 9\n\n#EOF -STATUS:CONFIRMED -DTSTART;TZID=Europe/Berlin:20200219T100000 -DTEND;TZID=Europe/Berlin:20200220T130000 -END:VEVENT -END:VCALENDAR diff --git a/tests/gehol/BA1.ics b/tests/gehol/BA1.ics deleted file mode 100644 index b866b50f..00000000 --- a/tests/gehol/BA1.ics +++ /dev/null @@ -1,1636 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//hacksw/handcal//NONSGML v1.0//EN - -BEGIN:VTIMEZONE -TZID:Europe/Brussels -X-LIC-LOCATION:Europe/Brussels -BEGIN:STANDARD -DTSTART:20111030T020000 -TZOFFSETFROM:+0200 -TZOFFSETTO:+0100 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 -TZNAME:CET -END:STANDARD -BEGIN:DAYLIGHT -DTSTART:20120325T030000 -TZOFFSETFROM:+0100 -TZOFFSETTO:+0200 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 -TZNAME:CEST -END:DAYLIGHT -END:VTIMEZONE - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130930T120000 -DTEND;TZID=Europe/Brussels:20130930T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.506, P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T120000 -DTEND;TZID=Europe/Brussels:20131007T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.506, P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131014T120000 -DTEND;TZID=Europe/Brussels:20131014T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.506, P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T120000 -DTEND;TZID=Europe/Brussels:20131021T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.506, P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131028T120000 -DTEND;TZID=Europe/Brussels:20131028T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.506, P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T120000 -DTEND;TZID=Europe/Brussels:20131104T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.506, P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T120000 -DTEND;TZID=Europe/Brussels:20131118T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.506, P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T120000 -DTEND;TZID=Europe/Brussels:20131125T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.506, P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T120000 -DTEND;TZID=Europe/Brussels:20131202T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.506, P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T120000 -DTEND;TZID=Europe/Brussels:20131209T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.506, P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131216T120000 -DTEND;TZID=Europe/Brussels:20131216T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.506, P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130923T100000 -DTEND;TZID=Europe/Brussels:20130923T120000 -SUMMARY: Programmation -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130930T100000 -DTEND;TZID=Europe/Brussels:20130930T120000 -SUMMARY: Programmation -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131014T100000 -DTEND;TZID=Europe/Brussels:20131014T120000 -SUMMARY: Programmation -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T100000 -DTEND;TZID=Europe/Brussels:20131021T120000 -SUMMARY: Programmation -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T100000 -DTEND;TZID=Europe/Brussels:20131118T120000 -SUMMARY: Programmation -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T100000 -DTEND;TZID=Europe/Brussels:20131125T120000 -SUMMARY: Programmation -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T100000 -DTEND;TZID=Europe/Brussels:20131202T120000 -SUMMARY: Programmation -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T100000 -DTEND;TZID=Europe/Brussels:20131209T120000 -SUMMARY: Programmation -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T120000 -DTEND;TZID=Europe/Brussels:20131118T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130923T140000 -DTEND;TZID=Europe/Brussels:20130923T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130930T140000 -DTEND;TZID=Europe/Brussels:20130930T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T140000 -DTEND;TZID=Europe/Brussels:20131007T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131014T140000 -DTEND;TZID=Europe/Brussels:20131014T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T140000 -DTEND;TZID=Europe/Brussels:20131021T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130916T090000 -DTEND;TZID=Europe/Brussels:20130916T120000 -SUMMARY: Accueil Facultaire Sciences -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T120000 -DTEND;TZID=Europe/Brussels:20131104T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T120000 -DTEND;TZID=Europe/Brussels:20131118T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T120000 -DTEND;TZID=Europe/Brussels:20131125T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T120000 -DTEND;TZID=Europe/Brussels:20131202T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T120000 -DTEND;TZID=Europe/Brussels:20131209T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131216T120000 -DTEND;TZID=Europe/Brussels:20131216T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140203T120000 -DTEND;TZID=Europe/Brussels:20140203T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140210T120000 -DTEND;TZID=Europe/Brussels:20140210T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140217T120000 -DTEND;TZID=Europe/Brussels:20140217T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140224T120000 -DTEND;TZID=Europe/Brussels:20140224T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140303T120000 -DTEND;TZID=Europe/Brussels:20140303T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140310T120000 -DTEND;TZID=Europe/Brussels:20140310T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140317T120000 -DTEND;TZID=Europe/Brussels:20140317T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140324T120000 -DTEND;TZID=Europe/Brussels:20140324T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140331T120000 -DTEND;TZID=Europe/Brussels:20140331T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140428T120000 -DTEND;TZID=Europe/Brussels:20140428T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140505T120000 -DTEND;TZID=Europe/Brussels:20140505T140000 -SUMMARY: Guidance en Physique -LOCATION: S.K.3.201 -DESCRIPTION: Professeur: Tlidi, Mustapha \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130916T140000 -DTEND;TZID=Europe/Brussels:20130916T180000 -SUMMARY: Accueil Facultaire Sciences -LOCATION: P.2N3.208b -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130917T100000 -DTEND;TZID=Europe/Brussels:20130917T120000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130924T100000 -DTEND;TZID=Europe/Brussels:20130924T120000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131001T100000 -DTEND;TZID=Europe/Brussels:20131001T120000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131008T100000 -DTEND;TZID=Europe/Brussels:20131008T120000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131015T100000 -DTEND;TZID=Europe/Brussels:20131015T120000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131022T100000 -DTEND;TZID=Europe/Brussels:20131022T120000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131105T100000 -DTEND;TZID=Europe/Brussels:20131105T120000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131112T100000 -DTEND;TZID=Europe/Brussels:20131112T120000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131119T100000 -DTEND;TZID=Europe/Brussels:20131119T120000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131126T100000 -DTEND;TZID=Europe/Brussels:20131126T120000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131203T100000 -DTEND;TZID=Europe/Brussels:20131203T120000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131210T100000 -DTEND;TZID=Europe/Brussels:20131210T120000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130918T120000 -DTEND;TZID=Europe/Brussels:20130918T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UB2.252A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130925T120000 -DTEND;TZID=Europe/Brussels:20130925T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UB2.252A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T140000 -DTEND;TZID=Europe/Brussels:20131023T160000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131002T120000 -DTEND;TZID=Europe/Brussels:20131002T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.607, P.2NO.707 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T120000 -DTEND;TZID=Europe/Brussels:20131009T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.607, P.2NO.707 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131016T120000 -DTEND;TZID=Europe/Brussels:20131016T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.607, P.2NO.707 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T120000 -DTEND;TZID=Europe/Brussels:20131023T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.607, P.2NO.707 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131030T120000 -DTEND;TZID=Europe/Brussels:20131030T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.607, P.2NO.707 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T120000 -DTEND;TZID=Europe/Brussels:20131106T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.607, P.2NO.707 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T120000 -DTEND;TZID=Europe/Brussels:20131113T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.607, P.2NO.707 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T120000 -DTEND;TZID=Europe/Brussels:20131127T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.607, P.2NO.707 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T120000 -DTEND;TZID=Europe/Brussels:20131204T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.607, P.2NO.707 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T120000 -DTEND;TZID=Europe/Brussels:20131211T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.607, P.2NO.707 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131218T120000 -DTEND;TZID=Europe/Brussels:20131218T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.607, P.2NO.707 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T140000 -DTEND;TZID=Europe/Brussels:20131009T160000 -SUMMARY: Mathématiques 1 -LOCATION: P.2NO906 -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T140000 -DTEND;TZID=Europe/Brussels:20131113T160000 -SUMMARY: Mathématiques 1 -LOCATION: P.2NO906 -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130918T080000 -DTEND;TZID=Europe/Brussels:20130918T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130925T080000 -DTEND;TZID=Europe/Brussels:20130925T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131002T080000 -DTEND;TZID=Europe/Brussels:20131002T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T080000 -DTEND;TZID=Europe/Brussels:20131009T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131016T080000 -DTEND;TZID=Europe/Brussels:20131016T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T080000 -DTEND;TZID=Europe/Brussels:20131023T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T080000 -DTEND;TZID=Europe/Brussels:20131106T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T080000 -DTEND;TZID=Europe/Brussels:20131113T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T080000 -DTEND;TZID=Europe/Brussels:20131127T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T080000 -DTEND;TZID=Europe/Brussels:20131204T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T080000 -DTEND;TZID=Europe/Brussels:20131211T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131218T080000 -DTEND;TZID=Europe/Brussels:20131218T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130918T100000 -DTEND;TZID=Europe/Brussels:20130918T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130925T100000 -DTEND;TZID=Europe/Brussels:20130925T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131002T100000 -DTEND;TZID=Europe/Brussels:20131002T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T100000 -DTEND;TZID=Europe/Brussels:20131009T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131016T100000 -DTEND;TZID=Europe/Brussels:20131016T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T100000 -DTEND;TZID=Europe/Brussels:20131023T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T100000 -DTEND;TZID=Europe/Brussels:20131106T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T100000 -DTEND;TZID=Europe/Brussels:20131113T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T100000 -DTEND;TZID=Europe/Brussels:20131127T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T100000 -DTEND;TZID=Europe/Brussels:20131204T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T100000 -DTEND;TZID=Europe/Brussels:20131211T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T120000 -DTEND;TZID=Europe/Brussels:20131009T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T120000 -DTEND;TZID=Europe/Brussels:20131023T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T120000 -DTEND;TZID=Europe/Brussels:20131106T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T120000 -DTEND;TZID=Europe/Brussels:20131113T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T120000 -DTEND;TZID=Europe/Brussels:20131127T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T120000 -DTEND;TZID=Europe/Brussels:20131204T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T120000 -DTEND;TZID=Europe/Brussels:20131211T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131218T120000 -DTEND;TZID=Europe/Brussels:20131218T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T140000 -DTEND;TZID=Europe/Brussels:20131211T160000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T080000 -DTEND;TZID=Europe/Brussels:20131128T100000 -SUMMARY: Programmation -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T080000 -DTEND;TZID=Europe/Brussels:20131205T100000 -SUMMARY: Programmation -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131003T140000 -DTEND;TZID=Europe/Brussels:20131003T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131010T140000 -DTEND;TZID=Europe/Brussels:20131010T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T080000 -DTEND;TZID=Europe/Brussels:20131121T100000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131212T080000 -DTEND;TZID=Europe/Brussels:20131212T100000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T120000 -DTEND;TZID=Europe/Brussels:20131128T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T120000 -DTEND;TZID=Europe/Brussels:20131205T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131212T120000 -DTEND;TZID=Europe/Brussels:20131212T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130919T140000 -DTEND;TZID=Europe/Brussels:20130919T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130919T080000 -DTEND;TZID=Europe/Brussels:20130919T100000 -SUMMARY: Programmation -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130926T080000 -DTEND;TZID=Europe/Brussels:20130926T100000 -SUMMARY: Programmation -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131003T080000 -DTEND;TZID=Europe/Brussels:20131003T100000 -SUMMARY: Programmation -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131010T080000 -DTEND;TZID=Europe/Brussels:20131010T100000 -SUMMARY: Programmation -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131017T080000 -DTEND;TZID=Europe/Brussels:20131017T100000 -SUMMARY: Programmation -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131024T080000 -DTEND;TZID=Europe/Brussels:20131024T100000 -SUMMARY: Programmation -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131107T080000 -DTEND;TZID=Europe/Brussels:20131107T100000 -SUMMARY: Programmation -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T080000 -DTEND;TZID=Europe/Brussels:20131114T100000 -SUMMARY: Programmation -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131031T113000 -DTEND;TZID=Europe/Brussels:20131031T133000 -SUMMARY: Programmation -LOCATION: P.FORUM.C, P.FORUM.A, P.FORUM.B -DESCRIPTION: Professeur: Massart, Thierry \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130926T140000 -DTEND;TZID=Europe/Brussels:20130926T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131017T140000 -DTEND;TZID=Europe/Brussels:20131017T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131024T140000 -DTEND;TZID=Europe/Brussels:20131024T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131107T140000 -DTEND;TZID=Europe/Brussels:20131107T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T140000 -DTEND;TZID=Europe/Brussels:20131114T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T140000 -DTEND;TZID=Europe/Brussels:20131121T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T140000 -DTEND;TZID=Europe/Brussels:20131128T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T140000 -DTEND;TZID=Europe/Brussels:20131205T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131212T140000 -DTEND;TZID=Europe/Brussels:20131212T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131219T140000 -DTEND;TZID=Europe/Brussels:20131219T160000 -SUMMARY: Fonctionnement des ordinateurs -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130919T160000 -DTEND;TZID=Europe/Brussels:20130919T180000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130920T120000 -DTEND;TZID=Europe/Brussels:20130920T140000 -SUMMARY: Didactique de la réussite -LOCATION: S.UA2.220 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131004T120000 -DTEND;TZID=Europe/Brussels:20131004T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.OF.2070, P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131011T120000 -DTEND;TZID=Europe/Brussels:20131011T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.OF.2070, P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131018T120000 -DTEND;TZID=Europe/Brussels:20131018T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.OF.2070, P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131025T120000 -DTEND;TZID=Europe/Brussels:20131025T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.OF.2070, P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131108T120000 -DTEND;TZID=Europe/Brussels:20131108T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.OF.2070, P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131115T120000 -DTEND;TZID=Europe/Brussels:20131115T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.OF.2070, P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131122T120000 -DTEND;TZID=Europe/Brussels:20131122T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.OF.2070, P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131129T120000 -DTEND;TZID=Europe/Brussels:20131129T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.OF.2070, P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131206T120000 -DTEND;TZID=Europe/Brussels:20131206T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.OF.2070, P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131213T120000 -DTEND;TZID=Europe/Brussels:20131213T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.OF.2070, P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131220T120000 -DTEND;TZID=Europe/Brussels:20131220T140000 -SUMMARY: Guidance en Mathématique -LOCATION: P.OF.2070, P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131011T120000 -DTEND;TZID=Europe/Brussels:20131011T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131018T120000 -DTEND;TZID=Europe/Brussels:20131018T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131025T120000 -DTEND;TZID=Europe/Brussels:20131025T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131108T120000 -DTEND;TZID=Europe/Brussels:20131108T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131115T120000 -DTEND;TZID=Europe/Brussels:20131115T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131122T120000 -DTEND;TZID=Europe/Brussels:20131122T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131129T120000 -DTEND;TZID=Europe/Brussels:20131129T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131206T120000 -DTEND;TZID=Europe/Brussels:20131206T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131213T120000 -DTEND;TZID=Europe/Brussels:20131213T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131220T120000 -DTEND;TZID=Europe/Brussels:20131220T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140214T120000 -DTEND;TZID=Europe/Brussels:20140214T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140221T120000 -DTEND;TZID=Europe/Brussels:20140221T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140228T120000 -DTEND;TZID=Europe/Brussels:20140228T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140307T120000 -DTEND;TZID=Europe/Brussels:20140307T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140314T120000 -DTEND;TZID=Europe/Brussels:20140314T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140321T120000 -DTEND;TZID=Europe/Brussels:20140321T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140328T120000 -DTEND;TZID=Europe/Brussels:20140328T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140404T120000 -DTEND;TZID=Europe/Brussels:20140404T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140425T120000 -DTEND;TZID=Europe/Brussels:20140425T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140502T120000 -DTEND;TZID=Europe/Brussels:20140502T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20140509T120000 -DTEND;TZID=Europe/Brussels:20140509T140000 -SUMMARY: Guidance en Physique -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130920T140000 -DTEND;TZID=Europe/Brussels:20130920T160000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131004T140000 -DTEND;TZID=Europe/Brussels:20131004T160000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131011T140000 -DTEND;TZID=Europe/Brussels:20131011T160000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131018T140000 -DTEND;TZID=Europe/Brussels:20131018T160000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131025T140000 -DTEND;TZID=Europe/Brussels:20131025T160000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131108T130000 -DTEND;TZID=Europe/Brussels:20131108T150000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131115T130000 -DTEND;TZID=Europe/Brussels:20131115T150000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131122T130000 -DTEND;TZID=Europe/Brussels:20131122T150000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131129T130000 -DTEND;TZID=Europe/Brussels:20131129T150000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131206T130000 -DTEND;TZID=Europe/Brussels:20131206T150000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131213T130000 -DTEND;TZID=Europe/Brussels:20131213T150000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131220T130000 -DTEND;TZID=Europe/Brussels:20131220T150000 -SUMMARY: Mathématiques 1 -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Federinov, Julien \n Assistant -END:VEVENT - - -END:VCALENDAR diff --git a/tests/gehol/BA2.ics b/tests/gehol/BA2.ics deleted file mode 100644 index 840ccaa3..00000000 --- a/tests/gehol/BA2.ics +++ /dev/null @@ -1,2239 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//hacksw/handcal//NONSGML v1.0//EN - -BEGIN:VTIMEZONE -TZID:Europe/Brussels -X-LIC-LOCATION:Europe/Brussels -BEGIN:STANDARD -DTSTART:20111030T020000 -TZOFFSETFROM:+0200 -TZOFFSETTO:+0100 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 -TZNAME:CET -END:STANDARD -BEGIN:DAYLIGHT -DTSTART:20120325T030000 -TZOFFSETFROM:+0100 -TZOFFSETTO:+0200 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 -TZNAME:CEST -END:DAYLIGHT -END:VTIMEZONE - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130930T120000 -DTEND;TZID=Europe/Brussels:20130930T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H2213 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T120000 -DTEND;TZID=Europe/Brussels:20131007T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H2213 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131014T120000 -DTEND;TZID=Europe/Brussels:20131014T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H2213 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T120000 -DTEND;TZID=Europe/Brussels:20131021T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H2213 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131216T180000 -DTEND;TZID=Europe/Brussels:20131216T200000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.K.1.105, S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant interro récap -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T120000 -DTEND;TZID=Europe/Brussels:20131104T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H1309 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T120000 -DTEND;TZID=Europe/Brussels:20131118T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H1309 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T120000 -DTEND;TZID=Europe/Brussels:20131125T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H1309 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T120000 -DTEND;TZID=Europe/Brussels:20131202T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H1309 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T140000 -DTEND;TZID=Europe/Brussels:20131007T160000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.R42.5.103 -DESCRIPTION: Professeur: \n Assistant Gr Sciences -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T120000 -DTEND;TZID=Europe/Brussels:20131209T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.R42.4.502 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130930T140000 -DTEND;TZID=Europe/Brussels:20130930T160000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.R42.5.107 -DESCRIPTION: Professeur: \n Assistant Gr Sciences -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130923T080000 -DTEND;TZID=Europe/Brussels:20130923T100000 -SUMMARY: Algorithmique 2 -LOCATION: P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130930T080000 -DTEND;TZID=Europe/Brussels:20130930T100000 -SUMMARY: Algorithmique 2 -LOCATION: P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T080000 -DTEND;TZID=Europe/Brussels:20131007T100000 -SUMMARY: Algorithmique 2 -LOCATION: P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131014T080000 -DTEND;TZID=Europe/Brussels:20131014T100000 -SUMMARY: Algorithmique 2 -LOCATION: P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T080000 -DTEND;TZID=Europe/Brussels:20131021T100000 -SUMMARY: Algorithmique 2 -LOCATION: P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131028T080000 -DTEND;TZID=Europe/Brussels:20131028T100000 -SUMMARY: Algorithmique 2 -LOCATION: P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130923T100000 -DTEND;TZID=Europe/Brussels:20130923T120000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: S.UD2.120 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130930T100000 -DTEND;TZID=Europe/Brussels:20130930T120000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: S.UD2.120 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T100000 -DTEND;TZID=Europe/Brussels:20131007T120000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: S.UD2.120 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131014T100000 -DTEND;TZID=Europe/Brussels:20131014T120000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: S.UD2.120 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T100000 -DTEND;TZID=Europe/Brussels:20131021T120000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: S.UD2.120 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131028T100000 -DTEND;TZID=Europe/Brussels:20131028T120000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: S.UD2.120 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T100000 -DTEND;TZID=Europe/Brussels:20131104T120000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: S.UD2.120 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T100000 -DTEND;TZID=Europe/Brussels:20131118T120000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: S.UD2.120 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T100000 -DTEND;TZID=Europe/Brussels:20131125T120000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: S.UD2.120 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T100000 -DTEND;TZID=Europe/Brussels:20131202T120000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: S.UD2.120 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T100000 -DTEND;TZID=Europe/Brussels:20131209T120000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: S.UD2.120 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131216T100000 -DTEND;TZID=Europe/Brussels:20131216T120000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: S.UD2.120 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T120000 -DTEND;TZID=Europe/Brussels:20131118T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130923T160000 -DTEND;TZID=Europe/Brussels:20130923T180000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130930T160000 -DTEND;TZID=Europe/Brussels:20130930T180000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T160000 -DTEND;TZID=Europe/Brussels:20131007T180000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131014T160000 -DTEND;TZID=Europe/Brussels:20131014T180000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T160000 -DTEND;TZID=Europe/Brussels:20131021T180000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131028T160000 -DTEND;TZID=Europe/Brussels:20131028T180000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T160000 -DTEND;TZID=Europe/Brussels:20131104T180000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T160000 -DTEND;TZID=Europe/Brussels:20131118T180000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T160000 -DTEND;TZID=Europe/Brussels:20131125T180000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T160000 -DTEND;TZID=Europe/Brussels:20131202T180000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T080000 -DTEND;TZID=Europe/Brussels:20131104T100000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T080000 -DTEND;TZID=Europe/Brussels:20131118T100000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T080000 -DTEND;TZID=Europe/Brussels:20131125T100000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T080000 -DTEND;TZID=Europe/Brussels:20131202T100000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T080000 -DTEND;TZID=Europe/Brussels:20131209T100000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131216T080000 -DTEND;TZID=Europe/Brussels:20131216T100000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T133000 -DTEND;TZID=Europe/Brussels:20131104T153000 -SUMMARY: Algorithmique 2 -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T133000 -DTEND;TZID=Europe/Brussels:20131118T153000 -SUMMARY: Algorithmique 2 -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T133000 -DTEND;TZID=Europe/Brussels:20131125T153000 -SUMMARY: Algorithmique 2 -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T133000 -DTEND;TZID=Europe/Brussels:20131202T153000 -SUMMARY: Algorithmique 2 -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T133000 -DTEND;TZID=Europe/Brussels:20131209T153000 -SUMMARY: Algorithmique 2 -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131216T133000 -DTEND;TZID=Europe/Brussels:20131216T153000 -SUMMARY: Algorithmique 2 -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131022T093000 -DTEND;TZID=Europe/Brussels:20131022T103000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.A2.122 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131008T093000 -DTEND;TZID=Europe/Brussels:20131008T103000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.A2.120 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131105T080000 -DTEND;TZID=Europe/Brussels:20131105T100000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.2NO4.008.PC -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131029T080000 -DTEND;TZID=Europe/Brussels:20131029T100000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131112T080000 -DTEND;TZID=Europe/Brussels:20131112T100000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131119T080000 -DTEND;TZID=Europe/Brussels:20131119T100000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131126T080000 -DTEND;TZID=Europe/Brussels:20131126T100000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131203T080000 -DTEND;TZID=Europe/Brussels:20131203T100000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131210T080000 -DTEND;TZID=Europe/Brussels:20131210T100000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131217T080000 -DTEND;TZID=Europe/Brussels:20131217T100000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130917T170000 -DTEND;TZID=Europe/Brussels:20130917T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130924T170000 -DTEND;TZID=Europe/Brussels:20130924T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131001T170000 -DTEND;TZID=Europe/Brussels:20131001T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131008T170000 -DTEND;TZID=Europe/Brussels:20131008T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131015T170000 -DTEND;TZID=Europe/Brussels:20131015T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131022T170000 -DTEND;TZID=Europe/Brussels:20131022T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131105T170000 -DTEND;TZID=Europe/Brussels:20131105T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131112T170000 -DTEND;TZID=Europe/Brussels:20131112T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131119T170000 -DTEND;TZID=Europe/Brussels:20131119T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131126T170000 -DTEND;TZID=Europe/Brussels:20131126T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131203T170000 -DTEND;TZID=Europe/Brussels:20131203T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131210T170000 -DTEND;TZID=Europe/Brussels:20131210T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131015T090000 -DTEND;TZID=Europe/Brussels:20131015T100000 -SUMMARY: Projets d'informatique 2 -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130924T103000 -DTEND;TZID=Europe/Brussels:20130924T123000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131001T103000 -DTEND;TZID=Europe/Brussels:20131001T123000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131008T103000 -DTEND;TZID=Europe/Brussels:20131008T123000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131015T103000 -DTEND;TZID=Europe/Brussels:20131015T123000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131022T103000 -DTEND;TZID=Europe/Brussels:20131022T123000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131105T103000 -DTEND;TZID=Europe/Brussels:20131105T123000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131112T103000 -DTEND;TZID=Europe/Brussels:20131112T123000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131119T103000 -DTEND;TZID=Europe/Brussels:20131119T123000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131126T103000 -DTEND;TZID=Europe/Brussels:20131126T123000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131203T103000 -DTEND;TZID=Europe/Brussels:20131203T123000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131210T103000 -DTEND;TZID=Europe/Brussels:20131210T123000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131217T103000 -DTEND;TZID=Europe/Brussels:20131217T123000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.2NO.708 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130917T133000 -DTEND;TZID=Europe/Brussels:20130917T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130924T133000 -DTEND;TZID=Europe/Brussels:20130924T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131001T133000 -DTEND;TZID=Europe/Brussels:20131001T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131008T133000 -DTEND;TZID=Europe/Brussels:20131008T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131022T133000 -DTEND;TZID=Europe/Brussels:20131022T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131029T133000 -DTEND;TZID=Europe/Brussels:20131029T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131105T133000 -DTEND;TZID=Europe/Brussels:20131105T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131112T133000 -DTEND;TZID=Europe/Brussels:20131112T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131119T133000 -DTEND;TZID=Europe/Brussels:20131119T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131126T133000 -DTEND;TZID=Europe/Brussels:20131126T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131203T133000 -DTEND;TZID=Europe/Brussels:20131203T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131218T080000 -DTEND;TZID=Europe/Brussels:20131218T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109 -DESCRIPTION: Professeur: Lucy, Gillian \n Assistant Rattrapage Mme Lucy -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130918T120000 -DTEND;TZID=Europe/Brussels:20130918T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UB2.252A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130925T120000 -DTEND;TZID=Europe/Brussels:20130925T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UB2.252A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T160000 -DTEND;TZID=Europe/Brussels:20131106T180000 -SUMMARY: Algorithmique 2 -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130925T080000 -DTEND;TZID=Europe/Brussels:20130925T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131002T080000 -DTEND;TZID=Europe/Brussels:20131002T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T080000 -DTEND;TZID=Europe/Brussels:20131009T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131016T080000 -DTEND;TZID=Europe/Brussels:20131016T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T080000 -DTEND;TZID=Europe/Brussels:20131023T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T080000 -DTEND;TZID=Europe/Brussels:20131106T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T080000 -DTEND;TZID=Europe/Brussels:20131113T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T080000 -DTEND;TZID=Europe/Brussels:20131127T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T080000 -DTEND;TZID=Europe/Brussels:20131204T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T080000 -DTEND;TZID=Europe/Brussels:20131211T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T120000 -DTEND;TZID=Europe/Brussels:20131009T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T120000 -DTEND;TZID=Europe/Brussels:20131023T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T120000 -DTEND;TZID=Europe/Brussels:20131106T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T120000 -DTEND;TZID=Europe/Brussels:20131113T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T120000 -DTEND;TZID=Europe/Brussels:20131127T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T120000 -DTEND;TZID=Europe/Brussels:20131204T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T120000 -DTEND;TZID=Europe/Brussels:20131211T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131218T120000 -DTEND;TZID=Europe/Brussels:20131218T140000 -SUMMARY: Introduction aux sciences de la terre -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Regnier, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T160000 -DTEND;TZID=Europe/Brussels:20131211T170000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.UB2.147 -DESCRIPTION: Professeur: \n Assistant Gr Sciences -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130918T080000 -DTEND;TZID=Europe/Brussels:20130918T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130925T080000 -DTEND;TZID=Europe/Brussels:20130925T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131002T080000 -DTEND;TZID=Europe/Brussels:20131002T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T080000 -DTEND;TZID=Europe/Brussels:20131009T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131016T080000 -DTEND;TZID=Europe/Brussels:20131016T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T080000 -DTEND;TZID=Europe/Brussels:20131023T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T080000 -DTEND;TZID=Europe/Brussels:20131106T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T080000 -DTEND;TZID=Europe/Brussels:20131113T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T080000 -DTEND;TZID=Europe/Brussels:20131127T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T080000 -DTEND;TZID=Europe/Brussels:20131204T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T080000 -DTEND;TZID=Europe/Brussels:20131211T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131218T080000 -DTEND;TZID=Europe/Brussels:20131218T100000 -SUMMARY: Histoire des sciences -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Marage, Pierre \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130918T100000 -DTEND;TZID=Europe/Brussels:20130918T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130925T100000 -DTEND;TZID=Europe/Brussels:20130925T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131002T100000 -DTEND;TZID=Europe/Brussels:20131002T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T100000 -DTEND;TZID=Europe/Brussels:20131009T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131016T100000 -DTEND;TZID=Europe/Brussels:20131016T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T100000 -DTEND;TZID=Europe/Brussels:20131023T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T100000 -DTEND;TZID=Europe/Brussels:20131106T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T100000 -DTEND;TZID=Europe/Brussels:20131113T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T100000 -DTEND;TZID=Europe/Brussels:20131127T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T100000 -DTEND;TZID=Europe/Brussels:20131204T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T100000 -DTEND;TZID=Europe/Brussels:20131211T120000 -SUMMARY: Société et environnement -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Decroly, Jean-Michel, Pattyn, Frank \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130918T120000 -DTEND;TZID=Europe/Brussels:20130918T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130925T120000 -DTEND;TZID=Europe/Brussels:20130925T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131002T120000 -DTEND;TZID=Europe/Brussels:20131002T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T120000 -DTEND;TZID=Europe/Brussels:20131009T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131016T120000 -DTEND;TZID=Europe/Brussels:20131016T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T120000 -DTEND;TZID=Europe/Brussels:20131023T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T120000 -DTEND;TZID=Europe/Brussels:20131106T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T120000 -DTEND;TZID=Europe/Brussels:20131113T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T120000 -DTEND;TZID=Europe/Brussels:20131127T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T120000 -DTEND;TZID=Europe/Brussels:20131204T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.JANSON -DESCRIPTION: Professeur: Castanheira De Moura, Micael \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131002T140000 -DTEND;TZID=Europe/Brussels:20131002T160000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.UD2.208 -DESCRIPTION: Professeur: \n Assistant Gr Sciences -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T140000 -DTEND;TZID=Europe/Brussels:20131009T160000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.UD2.208 -DESCRIPTION: Professeur: \n Assistant Gr Sciences -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131016T140000 -DTEND;TZID=Europe/Brussels:20131016T160000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.UD2.208 -DESCRIPTION: Professeur: \n Assistant Gr Sciences -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T140000 -DTEND;TZID=Europe/Brussels:20131023T160000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.UD2.208 -DESCRIPTION: Professeur: \n Assistant Gr Sciences -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T140000 -DTEND;TZID=Europe/Brussels:20131106T160000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.UD2.208 -DESCRIPTION: Professeur: \n Assistant Gr Sciences -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T140000 -DTEND;TZID=Europe/Brussels:20131113T160000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.UD2.208 -DESCRIPTION: Professeur: \n Assistant Gr Sciences -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T140000 -DTEND;TZID=Europe/Brussels:20131127T160000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.UD2.208 -DESCRIPTION: Professeur: \n Assistant Gr Sciences -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T140000 -DTEND;TZID=Europe/Brussels:20131204T160000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.UD2.208 -DESCRIPTION: Professeur: \n Assistant Gr Sciences -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T140000 -DTEND;TZID=Europe/Brussels:20131211T160000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.UD2.208 -DESCRIPTION: Professeur: \n Assistant Gr Sciences -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131218T140000 -DTEND;TZID=Europe/Brussels:20131218T160000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.UD2.208 -DESCRIPTION: Professeur: \n Assistant Gr Sciences -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131219T080000 -DTEND;TZID=Europe/Brussels:20131219T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109 -DESCRIPTION: Professeur: Lucy, Gillian \n Assistant Rattrapage Mme Lucy -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130926T100000 -DTEND;TZID=Europe/Brussels:20130926T130000 -SUMMARY: Langages de programmation 2 -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Berten, Vandy, Roggeman, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131003T100000 -DTEND;TZID=Europe/Brussels:20131003T130000 -SUMMARY: Langages de programmation 2 -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Berten, Vandy, Roggeman, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131010T100000 -DTEND;TZID=Europe/Brussels:20131010T130000 -SUMMARY: Langages de programmation 2 -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Berten, Vandy, Roggeman, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131017T100000 -DTEND;TZID=Europe/Brussels:20131017T130000 -SUMMARY: Langages de programmation 2 -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Berten, Vandy, Roggeman, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131024T100000 -DTEND;TZID=Europe/Brussels:20131024T130000 -SUMMARY: Langages de programmation 2 -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Berten, Vandy, Roggeman, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T140000 -DTEND;TZID=Europe/Brussels:20131114T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.Salle PC NO 4 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T140000 -DTEND;TZID=Europe/Brussels:20131121T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.Salle PC NO 4 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T140000 -DTEND;TZID=Europe/Brussels:20131128T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.Salle PC NO 4 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T140000 -DTEND;TZID=Europe/Brussels:20131205T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.Salle PC NO 4 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131212T140000 -DTEND;TZID=Europe/Brussels:20131212T160000 -SUMMARY: Systèmes d'exploitation -LOCATION: P.Salle PC NO 4 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130919T160000 -DTEND;TZID=Europe/Brussels:20130919T170000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131003T160000 -DTEND;TZID=Europe/Brussels:20131003T170000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131010T160000 -DTEND;TZID=Europe/Brussels:20131010T170000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131017T160000 -DTEND;TZID=Europe/Brussels:20131017T170000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131024T160000 -DTEND;TZID=Europe/Brussels:20131024T170000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131107T160000 -DTEND;TZID=Europe/Brussels:20131107T170000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T160000 -DTEND;TZID=Europe/Brussels:20131114T170000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T160000 -DTEND;TZID=Europe/Brussels:20131121T170000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T160000 -DTEND;TZID=Europe/Brussels:20131128T170000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T160000 -DTEND;TZID=Europe/Brussels:20131205T170000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131212T160000 -DTEND;TZID=Europe/Brussels:20131212T170000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130926T080000 -DTEND;TZID=Europe/Brussels:20130926T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.2.115, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131003T080000 -DTEND;TZID=Europe/Brussels:20131003T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.2.115, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131010T080000 -DTEND;TZID=Europe/Brussels:20131010T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.2.115, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131017T080000 -DTEND;TZID=Europe/Brussels:20131017T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.2.115, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131024T080000 -DTEND;TZID=Europe/Brussels:20131024T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.2.115, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131107T080000 -DTEND;TZID=Europe/Brussels:20131107T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.2.115, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T080000 -DTEND;TZID=Europe/Brussels:20131114T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.2.115, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T080000 -DTEND;TZID=Europe/Brussels:20131121T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.2.115, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T080000 -DTEND;TZID=Europe/Brussels:20131128T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.2.115, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T080000 -DTEND;TZID=Europe/Brussels:20131205T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.2.115, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131212T080000 -DTEND;TZID=Europe/Brussels:20131212T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.2.115, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Lucy, Gillian, Essex, Richard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130919T100000 -DTEND;TZID=Europe/Brussels:20130919T130000 -SUMMARY: Langages de programmation 2 -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Roggeman, Yves, Berten, Vandy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131107T100000 -DTEND;TZID=Europe/Brussels:20131107T130000 -SUMMARY: Langages de programmation 2 -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Roggeman, Yves, Berten, Vandy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T100000 -DTEND;TZID=Europe/Brussels:20131114T130000 -SUMMARY: Langages de programmation 2 -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Roggeman, Yves, Berten, Vandy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T100000 -DTEND;TZID=Europe/Brussels:20131121T130000 -SUMMARY: Langages de programmation 2 -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Roggeman, Yves, Berten, Vandy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T100000 -DTEND;TZID=Europe/Brussels:20131128T130000 -SUMMARY: Langages de programmation 2 -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Roggeman, Yves, Berten, Vandy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T100000 -DTEND;TZID=Europe/Brussels:20131205T130000 -SUMMARY: Langages de programmation 2 -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Roggeman, Yves, Berten, Vandy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131212T100000 -DTEND;TZID=Europe/Brussels:20131212T130000 -SUMMARY: Langages de programmation 2 -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Roggeman, Yves, Berten, Vandy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130926T160000 -DTEND;TZID=Europe/Brussels:20130926T170000 -SUMMARY: Calcul des probabilités et statistiques -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130919T170000 -DTEND;TZID=Europe/Brussels:20130919T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130926T170000 -DTEND;TZID=Europe/Brussels:20130926T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131003T170000 -DTEND;TZID=Europe/Brussels:20131003T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131010T170000 -DTEND;TZID=Europe/Brussels:20131010T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131017T170000 -DTEND;TZID=Europe/Brussels:20131017T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131024T170000 -DTEND;TZID=Europe/Brussels:20131024T190000 -SUMMARY: Analyse et méthodes -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: Hernalsteen, Christian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131004T080000 -DTEND;TZID=Europe/Brussels:20131004T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131011T080000 -DTEND;TZID=Europe/Brussels:20131011T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131018T080000 -DTEND;TZID=Europe/Brussels:20131018T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131025T080000 -DTEND;TZID=Europe/Brussels:20131025T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131108T080000 -DTEND;TZID=Europe/Brussels:20131108T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131115T080000 -DTEND;TZID=Europe/Brussels:20131115T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131122T080000 -DTEND;TZID=Europe/Brussels:20131122T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131129T080000 -DTEND;TZID=Europe/Brussels:20131129T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131206T080000 -DTEND;TZID=Europe/Brussels:20131206T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131213T080000 -DTEND;TZID=Europe/Brussels:20131213T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.P3.3.109, S.P1.1.111 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131115T140000 -DTEND;TZID=Europe/Brussels:20131115T160000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.DC2.206 -DESCRIPTION: Professeur: \n Assistant Gr Sciences -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130920T080000 -DTEND;TZID=Europe/Brussels:20130920T100000 -SUMMARY: Anglais scientifique I -LOCATION: S.UD2.120 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130920T100000 -DTEND;TZID=Europe/Brussels:20130920T120000 -SUMMARY: Algorithmique 2 -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Fortz, Bernard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131004T100000 -DTEND;TZID=Europe/Brussels:20131004T120000 -SUMMARY: Algorithmique 2 -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Fortz, Bernard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131011T100000 -DTEND;TZID=Europe/Brussels:20131011T120000 -SUMMARY: Algorithmique 2 -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Fortz, Bernard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131018T100000 -DTEND;TZID=Europe/Brussels:20131018T120000 -SUMMARY: Algorithmique 2 -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Fortz, Bernard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131025T100000 -DTEND;TZID=Europe/Brussels:20131025T120000 -SUMMARY: Algorithmique 2 -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Fortz, Bernard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131108T100000 -DTEND;TZID=Europe/Brussels:20131108T120000 -SUMMARY: Algorithmique 2 -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Fortz, Bernard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131115T100000 -DTEND;TZID=Europe/Brussels:20131115T120000 -SUMMARY: Algorithmique 2 -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Fortz, Bernard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131122T100000 -DTEND;TZID=Europe/Brussels:20131122T120000 -SUMMARY: Algorithmique 2 -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Fortz, Bernard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131129T100000 -DTEND;TZID=Europe/Brussels:20131129T120000 -SUMMARY: Algorithmique 2 -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Fortz, Bernard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131206T100000 -DTEND;TZID=Europe/Brussels:20131206T120000 -SUMMARY: Algorithmique 2 -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Fortz, Bernard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131213T100000 -DTEND;TZID=Europe/Brussels:20131213T120000 -SUMMARY: Algorithmique 2 -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Fortz, Bernard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131220T100000 -DTEND;TZID=Europe/Brussels:20131220T120000 -SUMMARY: Algorithmique 2 -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Fortz, Bernard \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131004T120000 -DTEND;TZID=Europe/Brussels:20131004T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H2214 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131011T120000 -DTEND;TZID=Europe/Brussels:20131011T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H2214 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131018T120000 -DTEND;TZID=Europe/Brussels:20131018T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H2214 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131025T120000 -DTEND;TZID=Europe/Brussels:20131025T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H2214 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131108T120000 -DTEND;TZID=Europe/Brussels:20131108T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H2214 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131115T120000 -DTEND;TZID=Europe/Brussels:20131115T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H2214 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131122T120000 -DTEND;TZID=Europe/Brussels:20131122T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H2214 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131129T120000 -DTEND;TZID=Europe/Brussels:20131129T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H2214 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131206T120000 -DTEND;TZID=Europe/Brussels:20131206T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H2214 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131213T120000 -DTEND;TZID=Europe/Brussels:20131213T140000 -SUMMARY: Introduction à la microéconomie -LOCATION: S.H2214 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -END:VCALENDAR diff --git a/tests/gehol/BA3.ics b/tests/gehol/BA3.ics deleted file mode 100644 index da54c032..00000000 --- a/tests/gehol/BA3.ics +++ /dev/null @@ -1,1951 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//hacksw/handcal//NONSGML v1.0//EN - -BEGIN:VTIMEZONE -TZID:Europe/Brussels -X-LIC-LOCATION:Europe/Brussels -BEGIN:STANDARD -DTSTART:20111030T020000 -TZOFFSETFROM:+0200 -TZOFFSETTO:+0100 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 -TZNAME:CET -END:STANDARD -BEGIN:DAYLIGHT -DTSTART:20120325T030000 -TZOFFSETFROM:+0100 -TZOFFSETTO:+0200 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 -TZNAME:CEST -END:DAYLIGHT -END:VTIMEZONE - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T100000 -DTEND;TZID=Europe/Brussels:20131007T130000 -SUMMARY: Modélisation et simulation -LOCATION: S.Salle.Baugniet (S.S.01.326) -DESCRIPTION: Professeur: Bontempi, Gianluca \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T100000 -DTEND;TZID=Europe/Brussels:20131202T130000 -SUMMARY: Modélisation et simulation -LOCATION: S.Salle.Baugniet (S.S.01.326) -DESCRIPTION: Professeur: Bontempi, Gianluca \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130916T100000 -DTEND;TZID=Europe/Brussels:20130916T130000 -SUMMARY: Modélisation et simulation -LOCATION: -DESCRIPTION: Professeur: Bontempi, Gianluca \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T100000 -DTEND;TZID=Europe/Brussels:20131104T130000 -SUMMARY: Modélisation et simulation -LOCATION: S.AW1.120 -DESCRIPTION: Professeur: Bontempi, Gianluca \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T100000 -DTEND;TZID=Europe/Brussels:20131118T130000 -SUMMARY: Modélisation et simulation -LOCATION: S.AW1.120 -DESCRIPTION: Professeur: Bontempi, Gianluca \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T100000 -DTEND;TZID=Europe/Brussels:20131125T130000 -SUMMARY: Modélisation et simulation -LOCATION: S.AW1.120 -DESCRIPTION: Professeur: Bontempi, Gianluca \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T100000 -DTEND;TZID=Europe/Brussels:20131209T130000 -SUMMARY: Modélisation et simulation -LOCATION: S.AW1.120 -DESCRIPTION: Professeur: Bontempi, Gianluca \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131216T100000 -DTEND;TZID=Europe/Brussels:20131216T130000 -SUMMARY: Modélisation et simulation -LOCATION: S.AW1.120 -DESCRIPTION: Professeur: Bontempi, Gianluca \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130923T160000 -DTEND;TZID=Europe/Brussels:20130923T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130930T160000 -DTEND;TZID=Europe/Brussels:20130930T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T160000 -DTEND;TZID=Europe/Brussels:20131007T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131014T160000 -DTEND;TZID=Europe/Brussels:20131014T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T160000 -DTEND;TZID=Europe/Brussels:20131021T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131028T160000 -DTEND;TZID=Europe/Brussels:20131028T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T160000 -DTEND;TZID=Europe/Brussels:20131104T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T160000 -DTEND;TZID=Europe/Brussels:20131118T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T160000 -DTEND;TZID=Europe/Brussels:20131125T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T160000 -DTEND;TZID=Europe/Brussels:20131202T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T100000 -DTEND;TZID=Europe/Brussels:20131021T130000 -SUMMARY: Modélisation et simulation -LOCATION: S.UA4.222 -DESCRIPTION: Professeur: Bontempi, Gianluca \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130916T160000 -DTEND;TZID=Europe/Brussels:20130916T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Kruys, Véronique, Vandenbranden, Michel \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130916T090000 -DTEND;TZID=Europe/Brussels:20130916T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.1.111 -DESCRIPTION: Professeur: Essex, Richard \n Assistant Introduction au cours -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131014T100000 -DTEND;TZID=Europe/Brussels:20131014T130000 -SUMMARY: Modélisation et simulation -LOCATION: S.R42.4.502 -DESCRIPTION: Professeur: Bontempi, Gianluca \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130916T160000 -DTEND;TZID=Europe/Brussels:20130916T180000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130923T160000 -DTEND;TZID=Europe/Brussels:20130923T180000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130930T160000 -DTEND;TZID=Europe/Brussels:20130930T180000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T160000 -DTEND;TZID=Europe/Brussels:20131007T180000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131014T160000 -DTEND;TZID=Europe/Brussels:20131014T180000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T160000 -DTEND;TZID=Europe/Brussels:20131021T180000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131028T160000 -DTEND;TZID=Europe/Brussels:20131028T180000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T160000 -DTEND;TZID=Europe/Brussels:20131104T180000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T160000 -DTEND;TZID=Europe/Brussels:20131118T180000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T160000 -DTEND;TZID=Europe/Brussels:20131125T180000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T160000 -DTEND;TZID=Europe/Brussels:20131202T180000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T160000 -DTEND;TZID=Europe/Brussels:20131209T180000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130923T080000 -DTEND;TZID=Europe/Brussels:20130923T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.211, S.P1.3.214, S.P1.3.116, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Essex, Richard, Ellefson, Eugene, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130930T080000 -DTEND;TZID=Europe/Brussels:20130930T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.211, S.P1.3.214, S.P1.3.116, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Essex, Richard, Ellefson, Eugene, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T080000 -DTEND;TZID=Europe/Brussels:20131007T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.211, S.P1.3.214, S.P1.3.116, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Essex, Richard, Ellefson, Eugene, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131014T080000 -DTEND;TZID=Europe/Brussels:20131014T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.211, S.P1.3.214, S.P1.3.116, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Essex, Richard, Ellefson, Eugene, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T080000 -DTEND;TZID=Europe/Brussels:20131021T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.211, S.P1.3.214, S.P1.3.116, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Essex, Richard, Ellefson, Eugene, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T080000 -DTEND;TZID=Europe/Brussels:20131104T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.211, S.P1.3.214, S.P1.3.116, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Essex, Richard, Ellefson, Eugene, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T080000 -DTEND;TZID=Europe/Brussels:20131118T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.211, S.P1.3.214, S.P1.3.116, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Essex, Richard, Ellefson, Eugene, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T080000 -DTEND;TZID=Europe/Brussels:20131125T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.211, S.P1.3.214, S.P1.3.116, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Essex, Richard, Ellefson, Eugene, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T080000 -DTEND;TZID=Europe/Brussels:20131202T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.211, S.P1.3.214, S.P1.3.116, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Essex, Richard, Ellefson, Eugene, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T080000 -DTEND;TZID=Europe/Brussels:20131209T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.211, S.P1.3.214, S.P1.3.116, S.P1.1.111 -DESCRIPTION: Professeur: Maitland, Leah, Essex, Richard, Ellefson, Eugene, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130923T100000 -DTEND;TZID=Europe/Brussels:20130923T130000 -SUMMARY: Modélisation et simulation -LOCATION: P.NO8 rotule -DESCRIPTION: Professeur: Bontempi, Gianluca \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130930T100000 -DTEND;TZID=Europe/Brussels:20130930T130000 -SUMMARY: Modélisation et simulation -LOCATION: P.NO8 rotule -DESCRIPTION: Professeur: Bontempi, Gianluca \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130916T140000 -DTEND;TZID=Europe/Brussels:20130916T160000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: Milojevic, Dragomir \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130923T140000 -DTEND;TZID=Europe/Brussels:20130923T160000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: Milojevic, Dragomir \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130930T140000 -DTEND;TZID=Europe/Brussels:20130930T160000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: Milojevic, Dragomir \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T140000 -DTEND;TZID=Europe/Brussels:20131007T160000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: Milojevic, Dragomir \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131014T140000 -DTEND;TZID=Europe/Brussels:20131014T160000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: Milojevic, Dragomir \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T140000 -DTEND;TZID=Europe/Brussels:20131021T160000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: Milojevic, Dragomir \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131028T140000 -DTEND;TZID=Europe/Brussels:20131028T160000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: Milojevic, Dragomir \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T140000 -DTEND;TZID=Europe/Brussels:20131104T160000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: Milojevic, Dragomir \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T140000 -DTEND;TZID=Europe/Brussels:20131118T160000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: Milojevic, Dragomir \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T140000 -DTEND;TZID=Europe/Brussels:20131125T160000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: Milojevic, Dragomir \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T140000 -DTEND;TZID=Europe/Brussels:20131202T160000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: Milojevic, Dragomir \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T140000 -DTEND;TZID=Europe/Brussels:20131209T160000 -SUMMARY: Circuits logiques et numériques -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: Milojevic, Dragomir \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130923T160000 -DTEND;TZID=Europe/Brussels:20130923T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214, S.P1.2.214 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130930T160000 -DTEND;TZID=Europe/Brussels:20130930T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214, S.P1.2.214 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T160000 -DTEND;TZID=Europe/Brussels:20131007T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214, S.P1.2.214 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131014T160000 -DTEND;TZID=Europe/Brussels:20131014T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214, S.P1.2.214 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T160000 -DTEND;TZID=Europe/Brussels:20131021T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214, S.P1.2.214 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T160000 -DTEND;TZID=Europe/Brussels:20131104T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214, S.P1.2.214 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T160000 -DTEND;TZID=Europe/Brussels:20131118T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214, S.P1.2.214 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T160000 -DTEND;TZID=Europe/Brussels:20131125T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214, S.P1.2.214 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T160000 -DTEND;TZID=Europe/Brussels:20131202T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214, S.P1.2.214 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T160000 -DTEND;TZID=Europe/Brussels:20131209T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214, S.P1.2.214 -DESCRIPTION: Professeur: Essex, Richard, Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130924T080000 -DTEND;TZID=Europe/Brussels:20130924T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214 -DESCRIPTION: Professeur: Ellefson, Eugene \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131001T080000 -DTEND;TZID=Europe/Brussels:20131001T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214 -DESCRIPTION: Professeur: Ellefson, Eugene \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131008T080000 -DTEND;TZID=Europe/Brussels:20131008T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214 -DESCRIPTION: Professeur: Ellefson, Eugene \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131015T080000 -DTEND;TZID=Europe/Brussels:20131015T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214 -DESCRIPTION: Professeur: Ellefson, Eugene \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131022T080000 -DTEND;TZID=Europe/Brussels:20131022T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214 -DESCRIPTION: Professeur: Ellefson, Eugene \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131029T080000 -DTEND;TZID=Europe/Brussels:20131029T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214 -DESCRIPTION: Professeur: Ellefson, Eugene \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131105T080000 -DTEND;TZID=Europe/Brussels:20131105T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214 -DESCRIPTION: Professeur: Ellefson, Eugene \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131112T080000 -DTEND;TZID=Europe/Brussels:20131112T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214 -DESCRIPTION: Professeur: Ellefson, Eugene \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131119T080000 -DTEND;TZID=Europe/Brussels:20131119T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214 -DESCRIPTION: Professeur: Ellefson, Eugene \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131126T080000 -DTEND;TZID=Europe/Brussels:20131126T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214 -DESCRIPTION: Professeur: Ellefson, Eugene \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131203T080000 -DTEND;TZID=Europe/Brussels:20131203T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214 -DESCRIPTION: Professeur: Ellefson, Eugene \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131210T080000 -DTEND;TZID=Europe/Brussels:20131210T100000 -SUMMARY: Anglais scientifique II -LOCATION: S.P1.3.214 -DESCRIPTION: Professeur: Ellefson, Eugene \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130924T100000 -DTEND;TZID=Europe/Brussels:20130924T130000 -SUMMARY: Réseaux -LOCATION: P.NO8 rotule -DESCRIPTION: Professeur: Leduc, Guy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131001T100000 -DTEND;TZID=Europe/Brussels:20131001T130000 -SUMMARY: Réseaux -LOCATION: P.NO8 rotule -DESCRIPTION: Professeur: Leduc, Guy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131029T130000 -DTEND;TZID=Europe/Brussels:20131029T160000 -SUMMARY: Biologie moléculaire et cellulaire -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Gueydan, Cyril \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131105T130000 -DTEND;TZID=Europe/Brussels:20131105T160000 -SUMMARY: Biologie moléculaire et cellulaire -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Gueydan, Cyril \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131112T130000 -DTEND;TZID=Europe/Brussels:20131112T160000 -SUMMARY: Biologie moléculaire et cellulaire -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Gueydan, Cyril \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131119T130000 -DTEND;TZID=Europe/Brussels:20131119T160000 -SUMMARY: Biologie moléculaire et cellulaire -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Gueydan, Cyril \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131126T130000 -DTEND;TZID=Europe/Brussels:20131126T160000 -SUMMARY: Biologie moléculaire et cellulaire -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Gueydan, Cyril \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131203T130000 -DTEND;TZID=Europe/Brussels:20131203T160000 -SUMMARY: Biologie moléculaire et cellulaire -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Gueydan, Cyril \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131210T130000 -DTEND;TZID=Europe/Brussels:20131210T160000 -SUMMARY: Biologie moléculaire et cellulaire -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Gueydan, Cyril \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130924T160000 -DTEND;TZID=Europe/Brussels:20130924T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.109 -DESCRIPTION: Professeur: Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131001T160000 -DTEND;TZID=Europe/Brussels:20131001T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.109 -DESCRIPTION: Professeur: Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131008T160000 -DTEND;TZID=Europe/Brussels:20131008T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.109 -DESCRIPTION: Professeur: Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131015T160000 -DTEND;TZID=Europe/Brussels:20131015T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.109 -DESCRIPTION: Professeur: Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131022T160000 -DTEND;TZID=Europe/Brussels:20131022T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.109 -DESCRIPTION: Professeur: Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131029T160000 -DTEND;TZID=Europe/Brussels:20131029T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.109 -DESCRIPTION: Professeur: Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131105T160000 -DTEND;TZID=Europe/Brussels:20131105T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.109 -DESCRIPTION: Professeur: Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131112T160000 -DTEND;TZID=Europe/Brussels:20131112T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.109 -DESCRIPTION: Professeur: Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131119T160000 -DTEND;TZID=Europe/Brussels:20131119T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.109 -DESCRIPTION: Professeur: Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131126T160000 -DTEND;TZID=Europe/Brussels:20131126T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.109 -DESCRIPTION: Professeur: Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131203T160000 -DTEND;TZID=Europe/Brussels:20131203T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.109 -DESCRIPTION: Professeur: Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131210T160000 -DTEND;TZID=Europe/Brussels:20131210T180000 -SUMMARY: Anglais scientifique II -LOCATION: S.P3.3.109 -DESCRIPTION: Professeur: Lucy, Gillian \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130917T080000 -DTEND;TZID=Europe/Brussels:20130917T100000 -SUMMARY: Electronique appliquée -LOCATION: S.UB5.230 -DESCRIPTION: Professeur: Robert, Frédéric \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130924T080000 -DTEND;TZID=Europe/Brussels:20130924T100000 -SUMMARY: Electronique appliquée -LOCATION: S.UB5.230 -DESCRIPTION: Professeur: Robert, Frédéric \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131001T080000 -DTEND;TZID=Europe/Brussels:20131001T100000 -SUMMARY: Electronique appliquée -LOCATION: S.UB5.230 -DESCRIPTION: Professeur: Robert, Frédéric \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131008T080000 -DTEND;TZID=Europe/Brussels:20131008T100000 -SUMMARY: Electronique appliquée -LOCATION: S.UB5.230 -DESCRIPTION: Professeur: Robert, Frédéric \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131015T080000 -DTEND;TZID=Europe/Brussels:20131015T100000 -SUMMARY: Electronique appliquée -LOCATION: S.UB5.230 -DESCRIPTION: Professeur: Robert, Frédéric \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131022T080000 -DTEND;TZID=Europe/Brussels:20131022T100000 -SUMMARY: Electronique appliquée -LOCATION: S.UB5.230 -DESCRIPTION: Professeur: Robert, Frédéric \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131105T080000 -DTEND;TZID=Europe/Brussels:20131105T100000 -SUMMARY: Electronique appliquée -LOCATION: S.UB5.230 -DESCRIPTION: Professeur: Robert, Frédéric \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131119T080000 -DTEND;TZID=Europe/Brussels:20131119T100000 -SUMMARY: Electronique appliquée -LOCATION: S.UB5.230 -DESCRIPTION: Professeur: Robert, Frédéric \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131126T080000 -DTEND;TZID=Europe/Brussels:20131126T100000 -SUMMARY: Electronique appliquée -LOCATION: S.UB5.230 -DESCRIPTION: Professeur: Robert, Frédéric \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131203T080000 -DTEND;TZID=Europe/Brussels:20131203T100000 -SUMMARY: Electronique appliquée -LOCATION: S.UB5.230 -DESCRIPTION: Professeur: Robert, Frédéric \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131210T080000 -DTEND;TZID=Europe/Brussels:20131210T100000 -SUMMARY: Electronique appliquée -LOCATION: S.UB5.230 -DESCRIPTION: Professeur: Robert, Frédéric \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131217T080000 -DTEND;TZID=Europe/Brussels:20131217T100000 -SUMMARY: Electronique appliquée -LOCATION: S.UB5.230 -DESCRIPTION: Professeur: Robert, Frédéric \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131029T100000 -DTEND;TZID=Europe/Brussels:20131029T120000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131112T100000 -DTEND;TZID=Europe/Brussels:20131112T120000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131119T100000 -DTEND;TZID=Europe/Brussels:20131119T120000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131126T100000 -DTEND;TZID=Europe/Brussels:20131126T120000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131203T100000 -DTEND;TZID=Europe/Brussels:20131203T120000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131210T100000 -DTEND;TZID=Europe/Brussels:20131210T120000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130917T160000 -DTEND;TZID=Europe/Brussels:20130917T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130924T160000 -DTEND;TZID=Europe/Brussels:20130924T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131001T160000 -DTEND;TZID=Europe/Brussels:20131001T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131008T160000 -DTEND;TZID=Europe/Brussels:20131008T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131015T160000 -DTEND;TZID=Europe/Brussels:20131015T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131022T160000 -DTEND;TZID=Europe/Brussels:20131022T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131029T160000 -DTEND;TZID=Europe/Brussels:20131029T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131105T160000 -DTEND;TZID=Europe/Brussels:20131105T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131112T160000 -DTEND;TZID=Europe/Brussels:20131112T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131119T160000 -DTEND;TZID=Europe/Brussels:20131119T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131126T160000 -DTEND;TZID=Europe/Brussels:20131126T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131203T160000 -DTEND;TZID=Europe/Brussels:20131203T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131210T160000 -DTEND;TZID=Europe/Brussels:20131210T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131016T140000 -DTEND;TZID=Europe/Brussels:20131016T160000 -SUMMARY: Mathématiques discrètes -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T140000 -DTEND;TZID=Europe/Brussels:20131023T160000 -SUMMARY: Mathématiques discrètes -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131030T140000 -DTEND;TZID=Europe/Brussels:20131030T160000 -SUMMARY: Mathématiques discrètes -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T140000 -DTEND;TZID=Europe/Brussels:20131106T160000 -SUMMARY: Mathématiques discrètes -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130925T160000 -DTEND;TZID=Europe/Brussels:20130925T180000 -SUMMARY: Electronique appliquée -LOCATION: -DESCRIPTION: Professeur: \n Assistant local S.UA5.217 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131002T160000 -DTEND;TZID=Europe/Brussels:20131002T180000 -SUMMARY: Electronique appliquée -LOCATION: -DESCRIPTION: Professeur: \n Assistant local S.UA5.217 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T160000 -DTEND;TZID=Europe/Brussels:20131009T180000 -SUMMARY: Electronique appliquée -LOCATION: -DESCRIPTION: Professeur: \n Assistant local S.UA5.217 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131016T160000 -DTEND;TZID=Europe/Brussels:20131016T180000 -SUMMARY: Electronique appliquée -LOCATION: -DESCRIPTION: Professeur: \n Assistant local S.UA5.217 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T160000 -DTEND;TZID=Europe/Brussels:20131023T180000 -SUMMARY: Electronique appliquée -LOCATION: -DESCRIPTION: Professeur: \n Assistant local S.UA5.217 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131030T160000 -DTEND;TZID=Europe/Brussels:20131030T180000 -SUMMARY: Electronique appliquée -LOCATION: -DESCRIPTION: Professeur: \n Assistant local S.UA5.217 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T160000 -DTEND;TZID=Europe/Brussels:20131106T180000 -SUMMARY: Electronique appliquée -LOCATION: -DESCRIPTION: Professeur: \n Assistant local S.UA5.217 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T160000 -DTEND;TZID=Europe/Brussels:20131113T180000 -SUMMARY: Electronique appliquée -LOCATION: -DESCRIPTION: Professeur: \n Assistant local S.UA5.217 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T160000 -DTEND;TZID=Europe/Brussels:20131127T180000 -SUMMARY: Electronique appliquée -LOCATION: -DESCRIPTION: Professeur: \n Assistant local S.UA5.217 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T160000 -DTEND;TZID=Europe/Brussels:20131204T180000 -SUMMARY: Electronique appliquée -LOCATION: -DESCRIPTION: Professeur: \n Assistant local S.UA5.217 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T160000 -DTEND;TZID=Europe/Brussels:20131211T180000 -SUMMARY: Electronique appliquée -LOCATION: -DESCRIPTION: Professeur: \n Assistant local S.UA5.217 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130918T140000 -DTEND;TZID=Europe/Brussels:20130918T160000 -SUMMARY: Mathématiques discrètes -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T140000 -DTEND;TZID=Europe/Brussels:20131113T160000 -SUMMARY: Mathématiques discrètes -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130925T140000 -DTEND;TZID=Europe/Brussels:20130925T160000 -SUMMARY: Mathématiques discrètes -LOCATION: P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T140000 -DTEND;TZID=Europe/Brussels:20131009T160000 -SUMMARY: Mathématiques discrètes -LOCATION: P.2NO.607 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T140000 -DTEND;TZID=Europe/Brussels:20131127T160000 -SUMMARY: Mathématiques discrètes -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T140000 -DTEND;TZID=Europe/Brussels:20131204T160000 -SUMMARY: Mathématiques discrètes -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T140000 -DTEND;TZID=Europe/Brussels:20131211T160000 -SUMMARY: Mathématiques discrètes -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131218T140000 -DTEND;TZID=Europe/Brussels:20131218T160000 -SUMMARY: Mathématiques discrètes -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131219T100000 -DTEND;TZID=Europe/Brussels:20131219T120000 -SUMMARY: Mathématiques discrètes -LOCATION: P.A2.122 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131024T140000 -DTEND;TZID=Europe/Brussels:20131024T160000 -SUMMARY: Modélisation et simulation -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T140000 -DTEND;TZID=Europe/Brussels:20131121T160000 -SUMMARY: Modélisation et simulation -LOCATION: P.2NO.707 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T080000 -DTEND;TZID=Europe/Brussels:20131121T100000 -SUMMARY: Génie logiciel et gestion de projets -LOCATION: P.FORUM.E -DESCRIPTION: Professeur: Van Der Straeten, Ragnhild \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131107T140000 -DTEND;TZID=Europe/Brussels:20131107T160000 -SUMMARY: Modélisation et simulation -LOCATION: P.2NO4.008.PC -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T140000 -DTEND;TZID=Europe/Brussels:20131114T160000 -SUMMARY: Modélisation et simulation -LOCATION: P.2NO4.008.PC -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131024T080000 -DTEND;TZID=Europe/Brussels:20131024T100000 -SUMMARY: Génie logiciel et gestion de projets -LOCATION: S.DC2.223 -DESCRIPTION: Professeur: Van Der Straeten, Ragnhild \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130919T100000 -DTEND;TZID=Europe/Brussels:20130919T120000 -SUMMARY: Génie logiciel et gestion de projets -LOCATION: S.DC2.223 -DESCRIPTION: Professeur: Van Der Straeten, Ragnhild \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130926T100000 -DTEND;TZID=Europe/Brussels:20130926T120000 -SUMMARY: Génie logiciel et gestion de projets -LOCATION: S.DC2.223 -DESCRIPTION: Professeur: Van Der Straeten, Ragnhild \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131003T100000 -DTEND;TZID=Europe/Brussels:20131003T120000 -SUMMARY: Génie logiciel et gestion de projets -LOCATION: S.DC2.223 -DESCRIPTION: Professeur: Van Der Straeten, Ragnhild \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131010T100000 -DTEND;TZID=Europe/Brussels:20131010T120000 -SUMMARY: Génie logiciel et gestion de projets -LOCATION: S.DC2.223 -DESCRIPTION: Professeur: Van Der Straeten, Ragnhild \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131017T100000 -DTEND;TZID=Europe/Brussels:20131017T120000 -SUMMARY: Génie logiciel et gestion de projets -LOCATION: S.DC2.223 -DESCRIPTION: Professeur: Van Der Straeten, Ragnhild \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131024T100000 -DTEND;TZID=Europe/Brussels:20131024T120000 -SUMMARY: Génie logiciel et gestion de projets -LOCATION: S.DC2.223 -DESCRIPTION: Professeur: Van Der Straeten, Ragnhild \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131003T140000 -DTEND;TZID=Europe/Brussels:20131003T160000 -SUMMARY: Modélisation et simulation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131010T140000 -DTEND;TZID=Europe/Brussels:20131010T160000 -SUMMARY: Modélisation et simulation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131017T140000 -DTEND;TZID=Europe/Brussels:20131017T160000 -SUMMARY: Modélisation et simulation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T080000 -DTEND;TZID=Europe/Brussels:20131128T100000 -SUMMARY: Modélisation et simulation -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T080000 -DTEND;TZID=Europe/Brussels:20131205T100000 -SUMMARY: Modélisation et simulation -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131212T080000 -DTEND;TZID=Europe/Brussels:20131212T100000 -SUMMARY: Modélisation et simulation -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131107T100000 -DTEND;TZID=Europe/Brussels:20131107T120000 -SUMMARY: Génie logiciel et gestion de projets -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Van Der Straeten, Ragnhild \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T100000 -DTEND;TZID=Europe/Brussels:20131114T120000 -SUMMARY: Génie logiciel et gestion de projets -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Van Der Straeten, Ragnhild \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T100000 -DTEND;TZID=Europe/Brussels:20131121T120000 -SUMMARY: Génie logiciel et gestion de projets -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Van Der Straeten, Ragnhild \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T100000 -DTEND;TZID=Europe/Brussels:20131128T120000 -SUMMARY: Génie logiciel et gestion de projets -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Van Der Straeten, Ragnhild \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T100000 -DTEND;TZID=Europe/Brussels:20131205T120000 -SUMMARY: Génie logiciel et gestion de projets -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Van Der Straeten, Ragnhild \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131212T100000 -DTEND;TZID=Europe/Brussels:20131212T120000 -SUMMARY: Génie logiciel et gestion de projets -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Van Der Straeten, Ragnhild \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131107T130000 -DTEND;TZID=Europe/Brussels:20131107T153000 -SUMMARY: Biologie moléculaire et cellulaire -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Gueydan, Cyril \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T130000 -DTEND;TZID=Europe/Brussels:20131114T153000 -SUMMARY: Biologie moléculaire et cellulaire -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Gueydan, Cyril \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T130000 -DTEND;TZID=Europe/Brussels:20131121T153000 -SUMMARY: Biologie moléculaire et cellulaire -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Gueydan, Cyril \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T130000 -DTEND;TZID=Europe/Brussels:20131128T153000 -SUMMARY: Biologie moléculaire et cellulaire -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Gueydan, Cyril \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T130000 -DTEND;TZID=Europe/Brussels:20131205T153000 -SUMMARY: Biologie moléculaire et cellulaire -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Gueydan, Cyril \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131219T130000 -DTEND;TZID=Europe/Brussels:20131219T153000 -SUMMARY: Biologie moléculaire et cellulaire -LOCATION: S.UD2.218A -DESCRIPTION: Professeur: Gueydan, Cyril \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131107T160000 -DTEND;TZID=Europe/Brussels:20131107T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T160000 -DTEND;TZID=Europe/Brussels:20131114T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T160000 -DTEND;TZID=Europe/Brussels:20131121T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T160000 -DTEND;TZID=Europe/Brussels:20131128T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T160000 -DTEND;TZID=Europe/Brussels:20131205T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131219T160000 -DTEND;TZID=Europe/Brussels:20131219T180000 -SUMMARY: Biochimie métabolique et structurale -LOCATION: P.FORUM.D -DESCRIPTION: Professeur: Vandenbranden, Michel, Kruys, Véronique \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130920T140000 -DTEND;TZID=Europe/Brussels:20130920T170000 -SUMMARY: Réseaux -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Leduc, Guy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131004T140000 -DTEND;TZID=Europe/Brussels:20131004T170000 -SUMMARY: Réseaux -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: Leduc, Guy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130920T080000 -DTEND;TZID=Europe/Brussels:20130920T100000 -SUMMARY: Mathématiques discrètes -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131004T080000 -DTEND;TZID=Europe/Brussels:20131004T100000 -SUMMARY: Mathématiques discrètes -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131011T080000 -DTEND;TZID=Europe/Brussels:20131011T100000 -SUMMARY: Mathématiques discrètes -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131018T080000 -DTEND;TZID=Europe/Brussels:20131018T100000 -SUMMARY: Mathématiques discrètes -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131025T080000 -DTEND;TZID=Europe/Brussels:20131025T100000 -SUMMARY: Mathématiques discrètes -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131108T080000 -DTEND;TZID=Europe/Brussels:20131108T100000 -SUMMARY: Mathématiques discrètes -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131115T080000 -DTEND;TZID=Europe/Brussels:20131115T100000 -SUMMARY: Mathématiques discrètes -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131122T080000 -DTEND;TZID=Europe/Brussels:20131122T100000 -SUMMARY: Mathématiques discrètes -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131129T080000 -DTEND;TZID=Europe/Brussels:20131129T100000 -SUMMARY: Mathématiques discrètes -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131206T080000 -DTEND;TZID=Europe/Brussels:20131206T100000 -SUMMARY: Mathématiques discrètes -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131213T080000 -DTEND;TZID=Europe/Brussels:20131213T100000 -SUMMARY: Mathématiques discrètes -LOCATION: P.2NO.506 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131011T140000 -DTEND;TZID=Europe/Brussels:20131011T170000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: Leduc, Guy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131018T140000 -DTEND;TZID=Europe/Brussels:20131018T170000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: Leduc, Guy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131108T140000 -DTEND;TZID=Europe/Brussels:20131108T170000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: Leduc, Guy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131115T140000 -DTEND;TZID=Europe/Brussels:20131115T170000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: Leduc, Guy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131122T140000 -DTEND;TZID=Europe/Brussels:20131122T170000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: Leduc, Guy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131129T140000 -DTEND;TZID=Europe/Brussels:20131129T170000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: Leduc, Guy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131206T140000 -DTEND;TZID=Europe/Brussels:20131206T170000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: Leduc, Guy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131213T140000 -DTEND;TZID=Europe/Brussels:20131213T170000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: Leduc, Guy \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131220T140000 -DTEND;TZID=Europe/Brussels:20131220T170000 -SUMMARY: Réseaux -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: Leduc, Guy \n Assistant -END:VEVENT - - -END:VCALENDAR diff --git a/tests/gehol/MA1.ics b/tests/gehol/MA1.ics deleted file mode 100644 index 041e0c8b..00000000 --- a/tests/gehol/MA1.ics +++ /dev/null @@ -1,1510 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//hacksw/handcal//NONSGML v1.0//EN - -BEGIN:VTIMEZONE -TZID:Europe/Brussels -X-LIC-LOCATION:Europe/Brussels -BEGIN:STANDARD -DTSTART:20111030T020000 -TZOFFSETFROM:+0200 -TZOFFSETTO:+0100 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 -TZNAME:CET -END:STANDARD -BEGIN:DAYLIGHT -DTSTART:20120325T030000 -TZOFFSETFROM:+0100 -TZOFFSETTO:+0200 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 -TZNAME:CEST -END:DAYLIGHT -END:VTIMEZONE - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130923T080000 -DTEND;TZID=Europe/Brussels:20130923T100000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T080000 -DTEND;TZID=Europe/Brussels:20131007T100000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T080000 -DTEND;TZID=Europe/Brussels:20131021T100000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T080000 -DTEND;TZID=Europe/Brussels:20131104T100000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T080000 -DTEND;TZID=Europe/Brussels:20131125T100000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T080000 -DTEND;TZID=Europe/Brussels:20131209T100000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130923T100000 -DTEND;TZID=Europe/Brussels:20130923T130000 -SUMMARY: Computability and complexity -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131007T100000 -DTEND;TZID=Europe/Brussels:20131007T130000 -SUMMARY: Computability and complexity -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131014T100000 -DTEND;TZID=Europe/Brussels:20131014T130000 -SUMMARY: Computability and complexity -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131021T100000 -DTEND;TZID=Europe/Brussels:20131021T130000 -SUMMARY: Computability and complexity -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131028T100000 -DTEND;TZID=Europe/Brussels:20131028T130000 -SUMMARY: Computability and complexity -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T100000 -DTEND;TZID=Europe/Brussels:20131104T130000 -SUMMARY: Computability and complexity -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T100000 -DTEND;TZID=Europe/Brussels:20131118T130000 -SUMMARY: Computability and complexity -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T100000 -DTEND;TZID=Europe/Brussels:20131125T130000 -SUMMARY: Computability and complexity -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T100000 -DTEND;TZID=Europe/Brussels:20131202T130000 -SUMMARY: Computability and complexity -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T100000 -DTEND;TZID=Europe/Brussels:20131209T130000 -SUMMARY: Computability and complexity -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131216T100000 -DTEND;TZID=Europe/Brussels:20131216T130000 -SUMMARY: Computability and complexity -LOCATION: P.FORUM.H -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T140000 -DTEND;TZID=Europe/Brussels:20131104T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Langerman, Stefan \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T140000 -DTEND;TZID=Europe/Brussels:20131118T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Langerman, Stefan \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T140000 -DTEND;TZID=Europe/Brussels:20131125T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Langerman, Stefan \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T140000 -DTEND;TZID=Europe/Brussels:20131202T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Langerman, Stefan \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T140000 -DTEND;TZID=Europe/Brussels:20131209T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Langerman, Stefan \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131216T140000 -DTEND;TZID=Europe/Brussels:20131216T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Langerman, Stefan \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130917T160000 -DTEND;TZID=Europe/Brussels:20130917T180000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Cardinal, Jean \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130924T160000 -DTEND;TZID=Europe/Brussels:20130924T180000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Cardinal, Jean \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131001T160000 -DTEND;TZID=Europe/Brussels:20131001T180000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Cardinal, Jean \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131008T160000 -DTEND;TZID=Europe/Brussels:20131008T180000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Cardinal, Jean \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131015T160000 -DTEND;TZID=Europe/Brussels:20131015T180000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Cardinal, Jean \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131022T160000 -DTEND;TZID=Europe/Brussels:20131022T180000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Cardinal, Jean \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131105T160000 -DTEND;TZID=Europe/Brussels:20131105T180000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Cardinal, Jean \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131112T160000 -DTEND;TZID=Europe/Brussels:20131112T180000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Cardinal, Jean \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131119T160000 -DTEND;TZID=Europe/Brussels:20131119T180000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Cardinal, Jean \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131126T160000 -DTEND;TZID=Europe/Brussels:20131126T180000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Cardinal, Jean \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131203T160000 -DTEND;TZID=Europe/Brussels:20131203T180000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Cardinal, Jean \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131210T160000 -DTEND;TZID=Europe/Brussels:20131210T180000 -SUMMARY: Data structures and algorithms -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Cardinal, Jean \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130924T160000 -DTEND;TZID=Europe/Brussels:20130924T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant VUB10F720 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131001T160000 -DTEND;TZID=Europe/Brussels:20131001T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant VUB10F720 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131008T160000 -DTEND;TZID=Europe/Brussels:20131008T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant VUB10F720 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131015T160000 -DTEND;TZID=Europe/Brussels:20131015T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant VUB10F720 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131022T160000 -DTEND;TZID=Europe/Brussels:20131022T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant VUB10F720 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131105T160000 -DTEND;TZID=Europe/Brussels:20131105T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant VUB10F720 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131112T160000 -DTEND;TZID=Europe/Brussels:20131112T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant VUB10F720 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131119T160000 -DTEND;TZID=Europe/Brussels:20131119T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant VUB10F720 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131126T160000 -DTEND;TZID=Europe/Brussels:20131126T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant VUB10F720 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131203T160000 -DTEND;TZID=Europe/Brussels:20131203T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant VUB10F720 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131210T160000 -DTEND;TZID=Europe/Brussels:20131210T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant VUB10F720 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131217T160000 -DTEND;TZID=Europe/Brussels:20131217T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant VUB10F720 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131105T160000 -DTEND;TZID=Europe/Brussels:20131105T180000 -SUMMARY: Decision Engineering -LOCATION: S.P4.1.10 (P4.1.303) -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131119T160000 -DTEND;TZID=Europe/Brussels:20131119T180000 -SUMMARY: Decision Engineering -LOCATION: S.P4.1.10 (P4.1.303) -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131126T160000 -DTEND;TZID=Europe/Brussels:20131126T180000 -SUMMARY: Decision Engineering -LOCATION: S.P4.1.10 (P4.1.303) -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131203T160000 -DTEND;TZID=Europe/Brussels:20131203T180000 -SUMMARY: Decision Engineering -LOCATION: S.P4.1.10 (P4.1.303) -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131210T160000 -DTEND;TZID=Europe/Brussels:20131210T180000 -SUMMARY: Decision Engineering -LOCATION: S.P4.1.10 (P4.1.303) -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131217T160000 -DTEND;TZID=Europe/Brussels:20131217T180000 -SUMMARY: Decision Engineering -LOCATION: S.P4.1.10 (P4.1.303) -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131218T150000 -DTEND;TZID=Europe/Brussels:20131218T170000 -SUMMARY: Decision Engineering -LOCATION: S.UB4.132 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T150000 -DTEND;TZID=Europe/Brussels:20131106T170000 -SUMMARY: Decision Engineering -LOCATION: S.C4.219 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T150000 -DTEND;TZID=Europe/Brussels:20131113T170000 -SUMMARY: Decision Engineering -LOCATION: S.C4.219 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T140000 -DTEND;TZID=Europe/Brussels:20131211T160000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.OF.2066 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T160000 -DTEND;TZID=Europe/Brussels:20131023T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant 2NO4.009 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131030T160000 -DTEND;TZID=Europe/Brussels:20131030T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant 2NO4.009 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T160000 -DTEND;TZID=Europe/Brussels:20131106T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant 2NO4.009 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T160000 -DTEND;TZID=Europe/Brussels:20131113T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant 2NO4.009 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131120T160000 -DTEND;TZID=Europe/Brussels:20131120T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant 2NO4.009 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T160000 -DTEND;TZID=Europe/Brussels:20131127T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant 2NO4.009 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T160000 -DTEND;TZID=Europe/Brussels:20131204T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant 2NO4.009 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T160000 -DTEND;TZID=Europe/Brussels:20131211T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant 2NO4.009 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131218T160000 -DTEND;TZID=Europe/Brussels:20131218T180000 -SUMMARY: Structure and interpretation of computer programs -LOCATION: -DESCRIPTION: Professeur: De Meuter, Wolfgang \n Assistant 2NO4.009 -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131002T080000 -DTEND;TZID=Europe/Brussels:20131002T100000 -SUMMARY: Operating systems II -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T080000 -DTEND;TZID=Europe/Brussels:20131009T100000 -SUMMARY: Operating systems II -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131016T080000 -DTEND;TZID=Europe/Brussels:20131016T100000 -SUMMARY: Operating systems II -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T080000 -DTEND;TZID=Europe/Brussels:20131023T100000 -SUMMARY: Operating systems II -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131030T080000 -DTEND;TZID=Europe/Brussels:20131030T100000 -SUMMARY: Operating systems II -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T080000 -DTEND;TZID=Europe/Brussels:20131106T100000 -SUMMARY: Operating systems II -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T080000 -DTEND;TZID=Europe/Brussels:20131113T100000 -SUMMARY: Operating systems II -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T080000 -DTEND;TZID=Europe/Brussels:20131127T100000 -SUMMARY: Operating systems II -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T080000 -DTEND;TZID=Europe/Brussels:20131204T100000 -SUMMARY: Operating systems II -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T080000 -DTEND;TZID=Europe/Brussels:20131211T100000 -SUMMARY: Operating systems II -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131218T080000 -DTEND;TZID=Europe/Brussels:20131218T100000 -SUMMARY: Operating systems II -LOCATION: P.OF.2072 -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T150000 -DTEND;TZID=Europe/Brussels:20131127T170000 -SUMMARY: Decision Engineering -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T150000 -DTEND;TZID=Europe/Brussels:20131204T170000 -SUMMARY: Decision Engineering -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T150000 -DTEND;TZID=Europe/Brussels:20131211T170000 -SUMMARY: Decision Engineering -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131030T150000 -DTEND;TZID=Europe/Brussels:20131030T170000 -SUMMARY: Decision Engineering -LOCATION: S.UB5.230 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130918T100000 -DTEND;TZID=Europe/Brussels:20130918T120000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130925T100000 -DTEND;TZID=Europe/Brussels:20130925T120000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131002T100000 -DTEND;TZID=Europe/Brussels:20131002T120000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T100000 -DTEND;TZID=Europe/Brussels:20131009T120000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131016T100000 -DTEND;TZID=Europe/Brussels:20131016T120000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T100000 -DTEND;TZID=Europe/Brussels:20131023T120000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131030T100000 -DTEND;TZID=Europe/Brussels:20131030T120000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T100000 -DTEND;TZID=Europe/Brussels:20131106T120000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T100000 -DTEND;TZID=Europe/Brussels:20131113T120000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T100000 -DTEND;TZID=Europe/Brussels:20131127T120000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T100000 -DTEND;TZID=Europe/Brussels:20131204T120000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T100000 -DTEND;TZID=Europe/Brussels:20131211T120000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131218T100000 -DTEND;TZID=Europe/Brussels:20131218T120000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: Geeraerts, Gilles \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130918T140000 -DTEND;TZID=Europe/Brussels:20130918T160000 -SUMMARY: Computer security -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Markowitch, Olivier \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130925T140000 -DTEND;TZID=Europe/Brussels:20130925T160000 -SUMMARY: Computer security -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Markowitch, Olivier \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T140000 -DTEND;TZID=Europe/Brussels:20131009T160000 -SUMMARY: Computer security -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Markowitch, Olivier \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131016T140000 -DTEND;TZID=Europe/Brussels:20131016T160000 -SUMMARY: Computer security -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Markowitch, Olivier \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T140000 -DTEND;TZID=Europe/Brussels:20131023T160000 -SUMMARY: Computer security -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Markowitch, Olivier \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131030T140000 -DTEND;TZID=Europe/Brussels:20131030T160000 -SUMMARY: Computer security -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Markowitch, Olivier \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T140000 -DTEND;TZID=Europe/Brussels:20131106T160000 -SUMMARY: Computer security -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Markowitch, Olivier \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T140000 -DTEND;TZID=Europe/Brussels:20131113T160000 -SUMMARY: Computer security -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Markowitch, Olivier \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T140000 -DTEND;TZID=Europe/Brussels:20131127T160000 -SUMMARY: Computer security -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Markowitch, Olivier \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T140000 -DTEND;TZID=Europe/Brussels:20131204T160000 -SUMMARY: Computer security -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Markowitch, Olivier \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T140000 -DTEND;TZID=Europe/Brussels:20131211T160000 -SUMMARY: Computer security -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Markowitch, Olivier \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131218T140000 -DTEND;TZID=Europe/Brussels:20131218T160000 -SUMMARY: Computer security -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: Markowitch, Olivier \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130918T160000 -DTEND;TZID=Europe/Brussels:20130918T180000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130925T160000 -DTEND;TZID=Europe/Brussels:20130925T180000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131002T160000 -DTEND;TZID=Europe/Brussels:20131002T180000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131009T160000 -DTEND;TZID=Europe/Brussels:20131009T180000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131016T160000 -DTEND;TZID=Europe/Brussels:20131016T180000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131023T160000 -DTEND;TZID=Europe/Brussels:20131023T180000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131030T160000 -DTEND;TZID=Europe/Brussels:20131030T180000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131106T160000 -DTEND;TZID=Europe/Brussels:20131106T180000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131113T160000 -DTEND;TZID=Europe/Brussels:20131113T180000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131127T160000 -DTEND;TZID=Europe/Brussels:20131127T180000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131204T160000 -DTEND;TZID=Europe/Brussels:20131204T180000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131211T160000 -DTEND;TZID=Europe/Brussels:20131211T180000 -SUMMARY: Introduction to language theory and compilation -LOCATION: P.FORUM.B -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T110000 -DTEND;TZID=Europe/Brussels:20131121T130000 -SUMMARY: Decision Engineering -LOCATION: S.H1308 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T110000 -DTEND;TZID=Europe/Brussels:20131128T130000 -SUMMARY: Decision Engineering -LOCATION: S.H1308 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131212T110000 -DTEND;TZID=Europe/Brussels:20131212T130000 -SUMMARY: Decision Engineering -LOCATION: S.H1308 -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130926T150000 -DTEND;TZID=Europe/Brussels:20130926T170000 -SUMMARY: Operating systems II -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T110000 -DTEND;TZID=Europe/Brussels:20131114T130000 -SUMMARY: Decision Engineering -LOCATION: S.Salle.Baugniet (S.S.01.326) -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130919T150000 -DTEND;TZID=Europe/Brussels:20130919T170000 -SUMMARY: Operating systems II -LOCATION: P.FORUM.C -DESCRIPTION: Professeur: Goossens, Joël \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131107T080000 -DTEND;TZID=Europe/Brussels:20131107T100000 -SUMMARY: Decision Engineering -LOCATION: S.UB4.136 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T080000 -DTEND;TZID=Europe/Brussels:20131114T100000 -SUMMARY: Decision Engineering -LOCATION: S.UB4.136 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T080000 -DTEND;TZID=Europe/Brussels:20131121T100000 -SUMMARY: Decision Engineering -LOCATION: S.UB4.136 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T080000 -DTEND;TZID=Europe/Brussels:20131128T100000 -SUMMARY: Decision Engineering -LOCATION: S.UB4.136 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T080000 -DTEND;TZID=Europe/Brussels:20131205T100000 -SUMMARY: Decision Engineering -LOCATION: S.UB4.136 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130926T130000 -DTEND;TZID=Europe/Brussels:20130926T170000 -SUMMARY: Learning dynamics -LOCATION: -DESCRIPTION: Professeur: Lenaerts, Tom, Nowe, Ann \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131003T130000 -DTEND;TZID=Europe/Brussels:20131003T170000 -SUMMARY: Learning dynamics -LOCATION: -DESCRIPTION: Professeur: Lenaerts, Tom, Nowe, Ann \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131010T130000 -DTEND;TZID=Europe/Brussels:20131010T170000 -SUMMARY: Learning dynamics -LOCATION: -DESCRIPTION: Professeur: Lenaerts, Tom, Nowe, Ann \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131017T130000 -DTEND;TZID=Europe/Brussels:20131017T170000 -SUMMARY: Learning dynamics -LOCATION: -DESCRIPTION: Professeur: Lenaerts, Tom, Nowe, Ann \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131024T130000 -DTEND;TZID=Europe/Brussels:20131024T170000 -SUMMARY: Learning dynamics -LOCATION: -DESCRIPTION: Professeur: Lenaerts, Tom, Nowe, Ann \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131031T130000 -DTEND;TZID=Europe/Brussels:20131031T170000 -SUMMARY: Learning dynamics -LOCATION: -DESCRIPTION: Professeur: Lenaerts, Tom, Nowe, Ann \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131107T130000 -DTEND;TZID=Europe/Brussels:20131107T170000 -SUMMARY: Learning dynamics -LOCATION: -DESCRIPTION: Professeur: Lenaerts, Tom, Nowe, Ann \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T130000 -DTEND;TZID=Europe/Brussels:20131114T170000 -SUMMARY: Learning dynamics -LOCATION: -DESCRIPTION: Professeur: Lenaerts, Tom, Nowe, Ann \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T130000 -DTEND;TZID=Europe/Brussels:20131121T170000 -SUMMARY: Learning dynamics -LOCATION: -DESCRIPTION: Professeur: Lenaerts, Tom, Nowe, Ann \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T130000 -DTEND;TZID=Europe/Brussels:20131128T170000 -SUMMARY: Learning dynamics -LOCATION: -DESCRIPTION: Professeur: Lenaerts, Tom, Nowe, Ann \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T130000 -DTEND;TZID=Europe/Brussels:20131205T170000 -SUMMARY: Learning dynamics -LOCATION: -DESCRIPTION: Professeur: Lenaerts, Tom, Nowe, Ann \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131212T130000 -DTEND;TZID=Europe/Brussels:20131212T170000 -SUMMARY: Learning dynamics -LOCATION: -DESCRIPTION: Professeur: Lenaerts, Tom, Nowe, Ann \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131107T120000 -DTEND;TZID=Europe/Brussels:20131107T140000 -SUMMARY: Decision Engineering -LOCATION: P.FORUM.G -DESCRIPTION: Professeur: De Smet, Yves \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131107T140000 -DTEND;TZID=Europe/Brussels:20131107T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T140000 -DTEND;TZID=Europe/Brussels:20131114T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T140000 -DTEND;TZID=Europe/Brussels:20131121T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T140000 -DTEND;TZID=Europe/Brussels:20131128T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T140000 -DTEND;TZID=Europe/Brussels:20131205T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131212T140000 -DTEND;TZID=Europe/Brussels:20131212T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131219T140000 -DTEND;TZID=Europe/Brussels:20131219T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131004T080000 -DTEND;TZID=Europe/Brussels:20131004T100000 -SUMMARY: Computer security -LOCATION: P.NO3.07A -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131004T100000 -DTEND;TZID=Europe/Brussels:20131004T120000 -SUMMARY: Operating systems II -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131018T100000 -DTEND;TZID=Europe/Brussels:20131018T120000 -SUMMARY: Operating systems II -LOCATION: P.OF.2070 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20130920T080000 -DTEND;TZID=Europe/Brussels:20130920T100000 -SUMMARY: Computer security -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131011T080000 -DTEND;TZID=Europe/Brussels:20131011T100000 -SUMMARY: Computer security -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131018T080000 -DTEND;TZID=Europe/Brussels:20131018T100000 -SUMMARY: Computer security -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131025T080000 -DTEND;TZID=Europe/Brussels:20131025T100000 -SUMMARY: Computer security -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131108T080000 -DTEND;TZID=Europe/Brussels:20131108T100000 -SUMMARY: Computer security -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131115T080000 -DTEND;TZID=Europe/Brussels:20131115T100000 -SUMMARY: Computer security -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131122T080000 -DTEND;TZID=Europe/Brussels:20131122T100000 -SUMMARY: Computer security -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131206T080000 -DTEND;TZID=Europe/Brussels:20131206T100000 -SUMMARY: Computer security -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131213T080000 -DTEND;TZID=Europe/Brussels:20131213T100000 -SUMMARY: Computer security -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131220T080000 -DTEND;TZID=Europe/Brussels:20131220T100000 -SUMMARY: Computer security -LOCATION: P.FORUM.F -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131115T100000 -DTEND;TZID=Europe/Brussels:20131115T120000 -SUMMARY: Operating systems II -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131122T100000 -DTEND;TZID=Europe/Brussels:20131122T120000 -SUMMARY: Operating systems II -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131206T100000 -DTEND;TZID=Europe/Brussels:20131206T120000 -SUMMARY: Operating systems II -LOCATION: P.FORUM.A -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -END:VCALENDAR diff --git a/tests/gehol/MA2.ics b/tests/gehol/MA2.ics deleted file mode 100644 index 535dae9a..00000000 --- a/tests/gehol/MA2.ics +++ /dev/null @@ -1,196 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//hacksw/handcal//NONSGML v1.0//EN - -BEGIN:VTIMEZONE -TZID:Europe/Brussels -X-LIC-LOCATION:Europe/Brussels -BEGIN:STANDARD -DTSTART:20111030T020000 -TZOFFSETFROM:+0200 -TZOFFSETTO:+0100 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 -TZNAME:CET -END:STANDARD -BEGIN:DAYLIGHT -DTSTART:20120325T030000 -TZOFFSETFROM:+0100 -TZOFFSETTO:+0200 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 -TZNAME:CEST -END:DAYLIGHT -END:VTIMEZONE - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T100000 -DTEND;TZID=Europe/Brussels:20131118T120000 -SUMMARY: Information technology in society -LOCATION: -DESCRIPTION: Professeur: Wilkin, Luc \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T100000 -DTEND;TZID=Europe/Brussels:20131104T120000 -SUMMARY: Information technology in society -LOCATION: S.H3228 -DESCRIPTION: Professeur: Wilkin, Luc \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T100000 -DTEND;TZID=Europe/Brussels:20131202T120000 -SUMMARY: Information technology in society -LOCATION: S.H3228 -DESCRIPTION: Professeur: Wilkin, Luc \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T100000 -DTEND;TZID=Europe/Brussels:20131125T120000 -SUMMARY: Information technology in society -LOCATION: S.R42.5.110 -DESCRIPTION: Professeur: Wilkin, Luc \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T100000 -DTEND;TZID=Europe/Brussels:20131209T120000 -SUMMARY: Information technology in society -LOCATION: S.UB4.136 -DESCRIPTION: Professeur: Wilkin, Luc \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131216T100000 -DTEND;TZID=Europe/Brussels:20131216T120000 -SUMMARY: Information technology in society -LOCATION: S.UB4.136 -DESCRIPTION: Professeur: Wilkin, Luc \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131104T140000 -DTEND;TZID=Europe/Brussels:20131104T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Langerman, Stefan \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131118T140000 -DTEND;TZID=Europe/Brussels:20131118T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Langerman, Stefan \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131125T140000 -DTEND;TZID=Europe/Brussels:20131125T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Langerman, Stefan \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131202T140000 -DTEND;TZID=Europe/Brussels:20131202T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Langerman, Stefan \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131209T140000 -DTEND;TZID=Europe/Brussels:20131209T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Langerman, Stefan \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131216T140000 -DTEND;TZID=Europe/Brussels:20131216T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: Langerman, Stefan \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131107T140000 -DTEND;TZID=Europe/Brussels:20131107T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131114T140000 -DTEND;TZID=Europe/Brussels:20131114T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131121T140000 -DTEND;TZID=Europe/Brussels:20131121T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131128T140000 -DTEND;TZID=Europe/Brussels:20131128T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131205T140000 -DTEND;TZID=Europe/Brussels:20131205T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131212T140000 -DTEND;TZID=Europe/Brussels:20131212T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -BEGIN:VEVENT -DTSTART;TZID=Europe/Brussels:20131219T140000 -DTEND;TZID=Europe/Brussels:20131219T160000 -SUMMARY: Computational geometry -LOCATION: P.OF.2078 -DESCRIPTION: Professeur: \n Assistant -END:VEVENT - - -END:VCALENDAR diff --git a/tests/grammar/__init__.py b/tests/grammar/__init__.py new file mode 100644 index 00000000..b81c0d0c --- /dev/null +++ b/tests/grammar/__init__.py @@ -0,0 +1,289 @@ +import re + +import pytest +from hypothesis import assume, given, example +from hypothesis.strategies import characters, text + +from ics.grammar import Container, ContentLine, ParseError, QuotedParamValue, escape_param, string_to_container, unfold_lines + +CONTROL = [chr(i) for i in range(ord(" ")) if i != ord("\t")] + [chr(0x7F)] +NAME = text(alphabet=(characters(whitelist_categories=["Lu"], whitelist_characters=["-"], max_codepoint=128)), min_size=1) +VALUE = text(characters(blacklist_categories=["Cs"], blacklist_characters=CONTROL)) + + +@pytest.mark.parametrize("inp, out", [ + ('HAHA:', ContentLine(name='HAHA', params={}, value='')), + ('HAHA:hoho', ContentLine(name='HAHA', params={}, value='hoho')), + ('HAHA:hoho:hihi', ContentLine(name='HAHA', params={}, value='hoho:hihi')), + ( + 'HAHA;hoho=1:hoho', + ContentLine(name='HAHA', params={'hoho': ['1']}, value='hoho') + ), ( + 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU', + ContentLine(name='RRULE', params={}, value='FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU') + ), ( + 'SUMMARY:dfqsdfjqkshflqsjdfhqs fqsfhlqs dfkqsldfkqsdfqsfqsfqsfs', + ContentLine(name='SUMMARY', params={}, value='dfqsdfjqkshflqsjdfhqs fqsfhlqs dfkqsldfkqsdfqsfqsfqsfs') + ), ( + 'DTSTART;TZID=Europe/Brussels:20131029T103000', + ContentLine(name='DTSTART', params={'TZID': ['Europe/Brussels']}, value='20131029T103000') + ), ( + 'haha;p2=v2;p1=v1:', + ContentLine(name='HAHA', params={'p2': ['v2'], 'p1': ['v1']}, value='') + ), ( + 'haha;hihi=p3,p4,p5;hoho=p1,p2:blabla:blublu', + ContentLine(name='HAHA', params={'hihi': ['p3', 'p4', 'p5'], 'hoho': ['p1', 'p2']}, value='blabla:blublu') + ), ( + 'ATTENDEE;X-A="I&rsquo\\;ll be in NYC":mailto:a@a.com', + ContentLine(name='ATTENDEE', params={'X-A': ['I&rsquo\\;ll be in NYC']}, value='mailto:a@a.com') + ), ( + 'DTEND;TZID="UTC":20190107T000000', + ContentLine(name='DTEND', params={'TZID': [QuotedParamValue('UTC')]}, value='20190107T000000') + ), ( + "ATTENDEE;MEMBER=\"mailto:ietf-calsch@example.org\":mailto:jsmith@example.com", + ContentLine("ATTENDEE", {"MEMBER": ["mailto:ietf-calsch@example.org"]}, "mailto:jsmith@example.com") + ), ( + "ATTENDEE;MEMBER=\"mailto:projectA@example.com\",\"mailto:projectB@example.com\":mailto:janedoe@example.com", + ContentLine("ATTENDEE", {"MEMBER": ["mailto:projectA@example.com", "mailto:projectB@example.com"]}, "mailto:janedoe@example.com") + ), ( + "RESOURCES:EASEL,PROJECTOR,VCR", + ContentLine("RESOURCES", value="EASEL,PROJECTOR,VCR") + ), ( + "ATTENDEE;CN=George Herman ^'Babe^' Ruth:mailto:babe@example.com", + ContentLine("ATTENDEE", {"CN": ["George Herman \"Babe\" Ruth"]}, "mailto:babe@example.com") + ), ( + "GEO;X-ADDRESS=Pittsburgh Pirates^n115 Federal St^nPittsburgh, PA 15212:40.446816,-80.00566", + ContentLine("GEO", {"X-ADDRESS": ["Pittsburgh Pirates\n115 Federal St\nPittsburgh", " PA 15212"]}, "40.446816,-80.00566") + ), ( + "GEO;X-ADDRESS=\"Pittsburgh Pirates^n115 Federal St^nPittsburgh, PA 15212\":40.446816,-80.00566", + ContentLine("GEO", {"X-ADDRESS": ["Pittsburgh Pirates\n115 Federal St\nPittsburgh, PA 15212"]}, "40.446816,-80.00566") + ), ( + "SUMMARY:Project XYZ Final Review\\nConference Room - 3B\\nCome Prepared.", + ContentLine("SUMMARY", value="Project XYZ Final Review\\nConference Room - 3B\\nCome Prepared.") + ), ( + "DESCRIPTION;ALTREP=\"cid:part1.0001@example.org\":The Fall'98 Wild Wizards Conference - - Las Vegas\\, NV\\, USA", + ContentLine("DESCRIPTION", {"ALTREP": ["cid:part1.0001@example.org"]}, value="The Fall'98 Wild Wizards Conference - - Las Vegas\\, NV\\, USA") + ), + +]) +def test_example_recode(inp, out): + par = ContentLine.parse(inp) + assert par == out + ser = out.serialize() + if inp[0].isupper(): + assert inp == ser + else: + assert inp.upper() == ser.upper() + par_ser = par.serialize() + if inp[0].isupper(): + assert inp == par_ser + else: + assert inp.upper() == par_ser.upper() + assert string_to_container(inp) == [out] + + +def test_param_quoting(): + inp = 'TEST;P1="A";P2=B;P3=C,"D",E,"F":"VAL"' + out = ContentLine("TEST", { + "P1": [QuotedParamValue("A")], + "P2": ["B"], + "P3": ["C", QuotedParamValue("D"), "E", QuotedParamValue("F")], + }, '"VAL"') + par = ContentLine.parse(inp) + assert par == out + ser = out.serialize() + assert inp == ser + par_ser = par.serialize() + assert inp == par_ser + assert string_to_container(inp) == [out] + + for param in out.params.keys(): + for o_val, p_val in zip(out[param], par[param]): + assert type(o_val) == type(p_val) + + +def test_trailing_escape_param(): + with pytest.raises(ValueError) as excinfo: + ContentLine.parse("TEST;PARAM=this ^^ is a ^'param^',with a ^trailing escape^:value") + assert "not end with an escape sequence" in str(excinfo.value) + assert ContentLine.parse("TEST;PARAM=this ^^ is a ^'param^',with a ^trailing escape:value").params["PARAM"] == \ + ["this ^ is a \"param\"", "with a ^trailing escape"] + + +@given(name=NAME, value=VALUE) +@example(name='A', value='abc\x85abc') +def test_any_name_value_recode(name, value): + raw = "%s:%s" % (name, value) + assert ContentLine.parse(raw).serialize() == raw + cl = ContentLine(name, value=value) + assert ContentLine.parse(cl.serialize()) == cl + assert string_to_container(raw) == [cl] + + +def quote_escape_param(pval): + if re.search("[:;,]", pval): + return '"%s"' % escape_param(pval) + else: + return escape_param(pval) + + +@given(param=NAME, value=VALUE) +def test_any_param_value_recode(param, value): + raw = "TEST;%s=%s:VALUE" % (param, quote_escape_param(value)) + assert ContentLine.parse(raw).serialize() == raw + cl = ContentLine("TEST", {param: [value]}, "VALUE") + assert ContentLine.parse(cl.serialize()) == cl + assert string_to_container(raw) == [cl] + + +@given(name=NAME, value=VALUE, + param1=NAME, p1value=VALUE, + param2=NAME, p2value1=VALUE, p2value2=VALUE) +def test_any_name_params_value_recode(name, value, param1, p1value, param2, p2value1, p2value2): + assume(param1 != param2) + raw = "%s;%s=%s;%s=%s,%s:%s" % (name, param1, quote_escape_param(p1value), + param2, quote_escape_param(p2value1), quote_escape_param(p2value2), value) + assert ContentLine.parse(raw).serialize() == raw + cl = ContentLine(name, {param1: [p1value], param2: [p2value1, p2value2]}, value) + assert ContentLine.parse(cl.serialize()) == cl + assert string_to_container(raw) == [cl] + + +def test_contentline_parse_error(): + pytest.raises(ParseError, ContentLine.parse, 'haha;p1=v1') + pytest.raises(ParseError, ContentLine.parse, 'haha;p1:') + + +def test_container(): + inp = """BEGIN:TEST +VAL1:The-Val +VAL2;PARAM1=P1;PARAM2=P2A,P2B;PARAM3="P3:A","P3:B,C":The-Val2 +END:TEST""" + out = Container('TEST', [ + ContentLine(name='VAL1', params={}, value='The-Val'), + ContentLine(name='VAL2', params={'PARAM1': ['P1'], 'PARAM2': ['P2A', 'P2B'], 'PARAM3': ['P3:A', 'P3:B,C']}, value='The-Val2')]) + + assert string_to_container(inp) == [out] + assert out.serialize() == inp.replace("\n", "\r\n") + assert str(out) == "TEST[VAL1='The-Val', VAL2{'PARAM1': ['P1'], 'PARAM2': ['P2A', 'P2B'], 'PARAM3': ['P3:A', 'P3:B,C']}='The-Val2']" + assert repr(out) == "Container('TEST', [ContentLine(name='VAL1', params={}, value='The-Val'), ContentLine(name='VAL2', params={'PARAM1': ['P1'], 'PARAM2': ['P2A', 'P2B'], 'PARAM3': ['P3:A', 'P3:B,C']}, value='The-Val2')])" + + out_shallow = out.clone(deep=False) + out_deep = out.clone(deep=True) + assert out == out_shallow == out_deep + assert all(a == b for a, b in zip(out, out_shallow)) + assert all(a == b for a, b in zip(out, out_deep)) + assert all(a is b for a, b in zip(out, out_shallow)) + assert all(a is not b for a, b in zip(out, out_deep)) + out_deep.append(ContentLine("LAST")) + assert out != out_deep + out[0].params["NEW"] = "SOMETHING" + assert out == out_shallow + out_shallow.name = "DIFFERENT" + assert out != out_shallow + + with pytest.raises(TypeError): + out_shallow[0] = ['CONTENT:Line'] + with pytest.raises(TypeError): + out_shallow[:] = ['CONTENT:Line'] + pytest.raises(TypeError, out_shallow.append, 'CONTENT:Line') + pytest.raises(TypeError, out_shallow.append, ['CONTENT:Line']) + pytest.raises(TypeError, out_shallow.extend, ['CONTENT:Line']) + out_shallow[:] = [out[0]] + assert out_shallow == Container("DIFFERENT", [out[0]]) + out_shallow[:] = [] + assert out_shallow == Container("DIFFERENT") + out_shallow.append(ContentLine("CL1")) + out_shallow.extend([ContentLine("CL3")]) + out_shallow.insert(1, ContentLine("CL2")) + out_shallow += [ContentLine("CL4")] + assert out_shallow[1:3] == Container("DIFFERENT", [ContentLine("CL2"), ContentLine("CL3")]) + assert out_shallow == Container("DIFFERENT", [ContentLine("CL1"), ContentLine("CL2"), ContentLine("CL3"), ContentLine("CL4")]) + + with pytest.warns(UserWarning, match="not all-uppercase"): + assert string_to_container("BEGIN:test\nEND:TeSt") == [Container("TEST", [])] + + +def test_container_nested(): + inp = """BEGIN:TEST1 +VAL1:The-Val +BEGIN:TEST2 +VAL2:The-Val +BEGIN:TEST3 +VAL3:The-Val +END:TEST3 +END:TEST2 +VAL4:The-Val +BEGIN:TEST2 +VAL5:The-Val +END:TEST2 +BEGIN:TEST2 +VAL5:The-Val +END:TEST2 +VAL6:The-Val +END:TEST1""" + out = Container('TEST1', [ + ContentLine(name='VAL1', params={}, value='The-Val'), + Container('TEST2', [ + ContentLine(name='VAL2', params={}, value='The-Val'), + Container('TEST3', [ + ContentLine(name='VAL3', params={}, value='The-Val') + ]) + ]), + ContentLine(name='VAL4', params={}, value='The-Val'), + Container('TEST2', [ + ContentLine(name='VAL5', params={}, value='The-Val')]), + Container('TEST2', [ + ContentLine(name='VAL5', params={}, value='The-Val')]), + ContentLine(name='VAL6', params={}, value='The-Val')]) + + assert string_to_container(inp) == [out] + assert out.serialize() == inp.replace("\n", "\r\n") + + +def test_container_parse_error(): + pytest.raises(ParseError, string_to_container, "BEGIN:TEST") + assert string_to_container("END:TEST") == [ContentLine(name="END", value="TEST")] + pytest.raises(ParseError, string_to_container, "BEGIN:TEST1\nEND:TEST2") + pytest.raises(ParseError, string_to_container, "BEGIN:TEST1\nEND:TEST2\nEND:TEST1") + assert string_to_container("BEGIN:TEST1\nEND:TEST1\nEND:TEST1") == [Container("TEST1"), ContentLine(name="END", value="TEST1")] + pytest.raises(ParseError, string_to_container, "BEGIN:TEST1\nBEGIN:TEST1\nEND:TEST1") + + +def test_unfold(): + val1 = "DESCRIPTION:This is a long description that exists on a long line." + val2 = "DESCRIPTION:This is a lo\n ng description\n that exists on a long line." + assert "".join(unfold_lines(val2.splitlines())) == val1 + assert string_to_container(val1) == string_to_container(val2) == [ContentLine.parse(val1)] + pytest.raises(ValueError, ContentLine.parse, val2) + + +def test_value_characters(): + chars = "abcABC0123456789" "-=_+!$%&*()[]{}<>'@#~/?|`¬€¨ÄÄää´ÁÁááßæÆ \t\\n😜🇪🇺👩🏾‍💻👨🏻‍👩🏻‍👧🏻‍👦🏻xyzXYZ" + special_chars = ";:,\"^" + inp = "TEST;P1={chars};P2={chars},{chars},\"{chars}\",{chars}:{chars}:{chars}{special}".format( + chars=chars, special=special_chars) + out = ContentLine("TEST", {"P1": [chars], "P2": [chars, chars, QuotedParamValue(chars), chars]}, + chars + ":" + chars + special_chars) + par = ContentLine.parse(inp) + assert par == out + ser = out.serialize() + assert inp == ser + par_ser = par.serialize() + assert inp == par_ser + assert string_to_container(inp) == [out] + + +def test_contentline_funcs(): + cl = ContentLine("TEST", {"PARAM": ["VAL"]}, "VALUE") + assert cl["PARAM"] == ["VAL"] + cl["PARAM2"] = ["VALA", "VALB"] + assert cl.params == {"PARAM": ["VAL"], "PARAM2": ["VALA", "VALB"]} + cl_clone = cl.clone() + assert cl == cl_clone and cl is not cl_clone + assert cl.params == cl_clone.params and cl.params is not cl_clone.params + assert cl.params["PARAM2"] == cl_clone.params["PARAM2"] and cl.params["PARAM2"] is not cl_clone.params["PARAM2"] + cl_clone["PARAM2"].append("VALC") + assert cl != cl_clone + assert str(cl) == "TEST{'PARAM': ['VAL'], 'PARAM2': ['VALA', 'VALB']}='VALUE'" + assert str(cl_clone) == "TEST{'PARAM': ['VAL'], 'PARAM2': ['VALA', 'VALB', 'VALC']}='VALUE'" diff --git a/tests/misc.py b/tests/misc.py deleted file mode 100644 index aad8f5bb..00000000 --- a/tests/misc.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -import unittest - -from ics.event import Event -from ics.icalendar import Calendar -from .fixture import cal32 - - -class TestEvent(unittest.TestCase): - def test_issue_90(self): - Calendar(cal32) - - def test_fixtures_case_meetup(self): - with open(os.path.join(os.path.dirname(__file__), "fixtures/case_meetup.ics")) as f: - Calendar(f.read()) - - def test_fixtures_encoding(self): - with open(os.path.join(os.path.dirname(__file__), "fixtures/encoding.ics")) as f: - Calendar(f.read()) - - def test_fixtures_groupscheduled(self): - with open(os.path.join(os.path.dirname(__file__), "fixtures/groupscheduled.ics")) as f: - Calendar(f.read()) - - def test_fixtures_multiple(self): - with open(os.path.join(os.path.dirname(__file__), "fixtures/multiple.ics")) as f: - Calendar.parse_multiple(f.read()) - - def test_fixtures_recurrence(self): - with open(os.path.join(os.path.dirname(__file__), "fixtures/recurrence.ics")) as f: - Calendar(f.read()) - - def test_fixtures_small(self): - with open(os.path.join(os.path.dirname(__file__), "fixtures/small.ics")) as f: - Calendar(f.read()) - - def test_fixtures_time(self): - with open(os.path.join(os.path.dirname(__file__), "fixtures/time.ics")) as f: - Calendar(f.read().replace("BEGIN:VCALENDAR", "BEGIN:VCALENDAR\nPRODID:Fixture")) - - def test_fixtures_timezoned(self): - with open(os.path.join(os.path.dirname(__file__), "fixtures/timezoned.ics")) as f: - Calendar(f.read()) - - def test_fixtures_utf_8_emoji(self): - with open(os.path.join(os.path.dirname(__file__), "fixtures/utf-8-emoji.ics")) as f: - Calendar(f.read()) - - def test_fixtures_romeo_juliet(self): - with open(os.path.join(os.path.dirname(__file__), "fixtures/Romeo-and-Juliet.ics")) as f: - event: Event = next(iter(Calendar(f.read()).events)) - with open(os.path.join(os.path.dirname(__file__), "fixtures/Romeo-and-Juliet.txt")) as f: - self.assertEqual(event.description, f.read()) - - def test_fixtures_spaces(self): - with open(os.path.join(os.path.dirname(__file__), "fixtures/spaces.ics")) as f: - Calendar(f.read()) diff --git a/tests/parse.py b/tests/parse.py deleted file mode 100644 index 0abc20be..00000000 --- a/tests/parse.py +++ /dev/null @@ -1,76 +0,0 @@ -import unittest - -from ics.icalendar import Calendar -from ics.grammar.parse import (Container, ContentLine, ParseError, lines_to_container, - string_to_container) - -from .fixture import cal1, cal5, cal11 - - -class TestParse(unittest.TestCase): - - def test_parse(self): - content = string_to_container(cal5) - self.assertEqual(1, len(content)) - - cal = content.pop() - self.assertEqual('VCALENDAR', cal.name) - self.assertTrue(isinstance(cal, Container)) - self.assertEqual('VERSION', cal[0].name) - self.assertEqual('2.0', cal[0].value) - self.assertEqual(cal5.strip().splitlines(), str(cal).strip().splitlines()) - - def test_one_line(self): - ics = 'DTSTART;TZID=Europe/Brussels:20131029T103000' - reader = lines_to_container([ics]) - self.assertEqual(next(iter(reader)), ContentLine( - 'DTSTART', - {'TZID': ['Europe/Brussels']}, - '20131029T103000' - )) - - def test_many_lines(self): - i = 0 - for line in string_to_container(cal1)[0]: - self.assertNotEqual('', line.name) - if isinstance(line, ContentLine): - self.assertNotEqual('', line.value) - if line.name == 'DESCRIPTION': - self.assertEqual('Lorem ipsum dolor sit amet, \ - consectetur adipiscing elit. \ - Sed vitae facilisis enim. \ - Morbi blandit et lectus venenatis tristique. \ - Donec sit amet egestas lacus. \ - Donec ullamcorper, mi vitae congue dictum, \ - quam dolor luctus augue, id cursus purus justo vel lorem. \ - Ut feugiat enim ipsum, quis porta nibh ultricies congue. \ - Pellentesque nisl mi, molestie id sem vel, \ - vehicula nullam.', line.value) - i += 1 - - def test_end_different(self): - - with self.assertRaises(ParseError): - Calendar(cal11) - - -class TestContainer(unittest.TestCase): - - def test_repr(self): - - e = ContentLine(name="VTEST", value="cocu !") - c = Container("test", e) - - self.assertEqual("", repr(c)) - - -class TestLine(unittest.TestCase): - - def test_repr(self): - - c = ContentLine(name="VTEST", value="cocu !") - self.assertEqual("", repr(c)) - - def test_get_item(self): - l = ContentLine(name="VTEST", value="cocu !", params={"plop": "plip"}) - self.assertEqual(l['plop'], "plip") diff --git a/tests/test.py b/tests/test.py deleted file mode 100644 index 050911e2..00000000 --- a/tests/test.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -import os -import unittest - -from ics.grammar.parse import string_to_container - - -class TestFunctional(unittest.TestCase): - - def test_gehol(self): - # convert ics to utf8: recode l9..utf8 *.ics - cal = os.path.join(os.path.dirname(__file__), "gehol", "BA1.ics") - with open(cal) as ics: - ics = ics.read() - ics = string_to_container(ics)[0] - self.assertTrue(ics) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/timeline.py b/tests/timeline.py deleted file mode 100644 index b7caf8ac..00000000 --- a/tests/timeline.py +++ /dev/null @@ -1,126 +0,0 @@ -import unittest -from datetime import datetime - -from ics.event import Event -from ics.icalendar import Calendar -from ics.timeline import Timeline - - -class TestTimeline(unittest.TestCase): - - def test_type(self): - - c = Calendar() - self.assertIsInstance(c.timeline, Timeline) - - def test_iter_is_ordered(self): - c = Calendar() - c.events.append(Event(begin=datetime.fromtimestamp(1236))) - c.events.append(Event(begin=datetime.fromtimestamp(1235))) - c.events.append(Event(begin=datetime.fromtimestamp(1234))) - - last = None - for event in c.timeline: - if last is not None: - self.assertGreaterEqual(event, last) - last = event - - def test_iter_over_all(self): - c = Calendar() - c.events.append(Event(begin=datetime.fromtimestamp(1234))) - c.events.append(Event(begin=datetime.fromtimestamp(1235))) - c.events.append(Event(begin=datetime.fromtimestamp(1236))) - - i = 0 - for event in c.timeline: - i += 1 - - self.assertEqual(i, 3) - - def test_iter_does_not_show_undefined_events(self): - c = Calendar() - - empty = Event() - c.events.append(empty) - c.events.append(Event(begin=datetime.fromtimestamp(1234))) - c.events.append(Event(begin=datetime.fromtimestamp(1235))) - - for event in c.timeline: - self.assertIsNot(empty, event) - - def test_included(self): - - c = Calendar() - - e = [ - Event(begin=datetime(2015, 10, 10)), - Event(begin=datetime(2010, 10, 10)), - Event(begin=datetime(2020, 10, 10)), - Event(begin=datetime(2015, 1, 10)), - Event(begin=datetime(2014, 1, 10), end=datetime(2018, 1, 10)), - ] - - for ev in e: - c.events.append(ev) - - included = list(c.timeline.included( - (datetime(2013, 10, 10)), - (datetime(2017, 10, 10)) - )) - self.assertSequenceEqual(included, [e[3]] + [e[0]]) - - def test_overlapping(self): - c = Calendar() - - e = [ - Event(begin=datetime(2010, 10, 10), end=datetime(2012, 10, 10)), - Event(begin=datetime(2013, 10, 10), end=datetime(2014, 10, 10)), - Event(begin=datetime(2016, 10, 10), end=datetime(2017, 10, 10)), - ] - - for ev in e: - c.events.append(ev) - - overlap = list(c.timeline.overlapping( - (datetime(2011, 10, 10)), - (datetime(2015, 10, 10)) - )) - self.assertSequenceEqual(overlap, [e[0]] + [e[1]]) - - def test_on(self): - - c = Calendar() - - e = [ - Event(begin=datetime(2015, 10, 10)), - Event(begin=datetime(2010, 10, 10)), - Event(begin=datetime(2020, 10, 10)), - Event(begin=datetime(2015, 1, 10)), - Event(begin=datetime(2014, 1, 10), end=datetime(2018, 1, 10)), - ] - - for ev in e: - c.events.append(ev) - - now = (datetime(2015, 10, 10, 12)) - on = list(c.timeline.on(now)) - self.assertSequenceEqual(on, [e[4], e[0]]) - - def test_on_strict(self): - - c = Calendar() - - e = [ - Event(begin=datetime(2015, 10, 10)), - Event(begin=datetime(2010, 10, 10)), - Event(begin=datetime(2020, 10, 10)), - Event(begin=datetime(2015, 1, 10)), - Event(begin=datetime(2014, 1, 10), end=datetime(2018, 1, 10)), - ] - - for ev in e: - c.events.append(ev) - - now = (datetime(2015, 10, 10, 12)) - on = list(c.timeline.on(now, strict=True)) - self.assertSequenceEqual(on, [e[0]]) diff --git a/tests/timespan.py b/tests/timespan.py deleted file mode 100644 index dc6d6348..00000000 --- a/tests/timespan.py +++ /dev/null @@ -1,121 +0,0 @@ -import itertools -from datetime import datetime as dt, timedelta as td - -import dateutil -import pytest - -from ics.timespan import EventTimespan -from ics.utils import floor_datetime_to_midnight - - -class TestEventTimespan(object): - data = [] - - def assert_end_eq_duration(self, by_dur: EventTimespan, by_end: EventTimespan): - - assert by_dur.get_begin() == by_end.get_begin() - assert by_dur.get_effective_end() == by_end.get_effective_end() - assert by_dur.get_effective_duration() == by_end.get_effective_duration() - assert by_dur.get_precision() == by_end.get_precision() - assert by_dur == by_end.convert_end("duration") - assert by_end == by_dur.convert_end("end") - - def assert_make_all_day_valid(self, ts_secs: EventTimespan, ts_days: EventTimespan): - # check resolution for all_day - assert ts_days.get_effective_duration() % td(days=1) == td(0) - assert floor_datetime_to_midnight(ts_days.get_begin()) == ts_days.get_begin() - assert floor_datetime_to_midnight(ts_days.get_effective_end()) == ts_days.get_effective_end() - - # minimum duration is 0s / 1d - assert ts_secs.get_effective_duration() >= td() - assert ts_days.get_effective_duration() >= td(days=1) - - # test inclusion and boundaries - ts_days_begin_wtz = ts_days.get_begin().replace(tzinfo=ts_secs.get_begin().tzinfo) - ts_days_eff_end_wtz = ts_days.get_effective_end().replace(tzinfo=ts_secs.get_effective_end().tzinfo) - assert ts_days_begin_wtz <= ts_secs.get_begin() - assert ts_days_eff_end_wtz >= ts_secs.get_effective_end() - assert ts_days.get_effective_duration() >= ts_secs.get_effective_duration() - - # test that we didn't decrease anything - begin_earlier = ts_secs.get_begin() - ts_days_begin_wtz - end_later = ts_days_eff_end_wtz - ts_secs.get_effective_end() - duration_bigger = ts_days.get_effective_duration() - ts_secs.get_effective_duration() - assert begin_earlier >= td() - assert end_later >= td() - assert duration_bigger >= td() - - # test that we didn't drift too far - assert begin_earlier < td(hours=24) - instant_to_one_day = (ts_secs.get_begin().hour == 0 and ts_secs.get_effective_duration() == td()) - if instant_to_one_day: - assert end_later == td(hours=24) - else: - assert end_later < td(hours=24) - # NOTICE: duration might grow by 48h, not only 24, as we floor the begin time (which might be 23:59) - # and ceil the end time (which might be 00:01) - assert duration_bigger < td(hours=24 * 2) - - # test that we made no unnecessary modification - if ts_secs.get_begin() == floor_datetime_to_midnight(ts_secs.get_begin()): - assert ts_days.get_begin() == ts_secs.get_begin().replace(tzinfo=None) - if ts_secs.get_effective_end() == floor_datetime_to_midnight(ts_secs.get_effective_end()): - if instant_to_one_day: - # here we need to convert duration=0 to duration=1d - assert ts_days.get_effective_end() == ts_secs.get_effective_end().replace(tzinfo=None) + td(days=1) - else: - assert ts_days.get_effective_end() == ts_secs.get_effective_end().replace(tzinfo=None) - - # the following won't hold for events that don't start at 00:00, compare NOTICE above - if ts_secs.get_begin().hour == 0: - if instant_to_one_day: - # here we need to convert duration=0 to duration=1d - assert duration_bigger == td(hours=24) - else: - # if we start at midnight, only the end time is ceiled, which can only add up to 24h instead of 48h - assert duration_bigger < td(hours=24) - - mod_duration = (ts_secs.get_effective_duration() % td(days=1)) - if ts_secs.get_effective_duration() <= td(days=1): - # here we need to convert duration<1d to duration=1d - assert ts_days.get_effective_duration() == td(days=1) - elif mod_duration == td(): - assert ts_days.get_effective_duration() == ts_secs.get_effective_duration() - else: - assert ts_days.get_effective_duration() == ts_secs.get_effective_duration() + td(days=1) - mod_duration - - # log data - if self.data is not None: - self.data.append(( - str(ts_secs), ts_secs.get_effective_duration().days * 24 + ts_secs.get_effective_duration().seconds / 3600, - str(ts_days), ts_days.get_effective_duration().days * 24 + ts_days.get_effective_duration().seconds / 3600, - begin_earlier, end_later, duration_bigger, - )) - - # this generates quite a lot of different dates, but make_all_day is hard, so better test more corner cases than less - @pytest.mark.parametrize(["begin_tz", "begin_hour", "dur_hours"], [ - (begin_tz, begin_hour, dur_hours) - for begin_tz in range(-3, 3) - for begin_hour in range(-3, 3) - for dur_hours in itertools.chain(range(0, 5), range(24 - 4, 24 + 5), range(48 - 4, 48 + 5)) - ]) - def test(self, begin_tz, begin_hour, dur_hours): - tzoffset = dateutil.tz.tzoffset("%+03d:00" % begin_tz, td(hours=begin_tz)) - start = dt(2019, 5, 29, tzinfo=tzoffset) + td(hours=begin_hour) - timespan_seconds = EventTimespan(begin_time=start, duration=td(hours=dur_hours)) - timespan_all_day = timespan_seconds.make_all_day() - - timespan_seconds_end = EventTimespan(begin_time=start, end_time=start + td(hours=dur_hours)) - timespan_all_day_end = timespan_seconds_end.make_all_day() - - # TODO none of the following will hold if begin_tz and end_tz differ - e.g. for plane flights - self.assert_end_eq_duration(timespan_seconds, timespan_seconds_end) - self.assert_end_eq_duration(timespan_all_day, timespan_all_day_end) - self.assert_make_all_day_valid(timespan_seconds, timespan_all_day) - - # for end_tz in range(-3, 3): - - # from tabulate import tabulate - # if self.data: - # print(tabulate(self.data, headers=("hourly event", "hourly duration", "all-day event", "all-day duration", - # "begins earlier by", "ends later by", "duration bigger by"))) diff --git a/tests/todo.py b/tests/todo.py deleted file mode 100644 index 91c24c9b..00000000 --- a/tests/todo.py +++ /dev/null @@ -1,357 +0,0 @@ -import unittest -from datetime import datetime, datetime as dt, timedelta, timezone - -from dateutil.tz import UTC as dateutil_tzutc - -from ics.alarm.display import DisplayAlarm -from ics.grammar.parse import Container -from ics.icalendar import Calendar -from ics.todo import Todo -from .fixture import cal27, cal28, cal29, cal30, cal31 - -datetime_tzutc = timezone.utc - -CRLF = "\r\n" - - -class TestTodo(unittest.TestCase): - maxDiff = None - - def test_init(self): - t = Todo() - self.assertIsNotNone(t.uid) - self.assertIsNotNone(t.dtstamp) - self.assertIsNone(t.completed) - self.assertIsNone(t.created) - self.assertIsNone(t.description) - self.assertIsNone(t.begin) - self.assertIsNone(t.location) - self.assertIsNone(t.percent) - self.assertIsNone(t.priority) - self.assertIsNone(t.name) - self.assertIsNone(t.url) - self.assertIsNone(t.status) - self.assertEqual(t.extra, Container(name='VTODO')) - - def test_init_non_exclusive_arguments(self): - # attributes percent, priority, begin, due, and duration - # aren't tested here - dtstamp = datetime(2018, 2, 18, 12, 19, tzinfo=datetime_tzutc) - completed = dtstamp + timedelta(days=1) - created = dtstamp + timedelta(seconds=1) - alarms = [DisplayAlarm] - - t = Todo( - uid='uid', - dtstamp=dtstamp, - completed=completed, - created=created, - description='description', - location='location', - name='name', - url='url', - alarms=alarms) - - self.assertEqual(t.uid, 'uid') - self.assertEqual(t.dtstamp, dtstamp) - self.assertEqual(t.completed, completed) - self.assertEqual(t.created, created) - self.assertEqual(t.description, 'description') - self.assertEqual(t.location, 'location') - self.assertEqual(t.name, 'name') - self.assertEqual(t.url, 'url') - self.assertEqual(t.alarms, alarms) - - def test_percent(self): - t1 = Todo(percent=0) - self.assertEqual(t1.percent, 0) - t2 = Todo(percent=100) - self.assertEqual(t2.percent, 100) - with self.assertRaises(ValueError): - Todo(percent=-1) - with self.assertRaises(ValueError): - Todo(percent=101) - - def test_priority(self): - t1 = Todo(priority=0) - self.assertEqual(t1.priority, 0) - t2 = Todo(priority=9) - self.assertEqual(t2.priority, 9) - with self.assertRaises(ValueError): - Todo(priority=-1) - with self.assertRaises(ValueError): - Todo(priority=10) - - def test_begin(self): - begin = datetime(2018, 2, 18, 12, 19, tzinfo=datetime_tzutc) - t = Todo(begin=begin) - self.assertEqual(t.begin, begin) - - # begin after due - t = Todo(due=datetime.fromtimestamp(1)) - with self.assertRaises(ValueError): - t.begin = datetime.fromtimestamp(2) - - def test_duration(self): - begin = datetime(2018, 2, 18, 12, 19, tzinfo=datetime_tzutc) - t1 = Todo(begin=begin, duration={'hours': 1}) - self.assertEqual(t1.duration, timedelta(hours=1)) - t2 = Todo(begin=begin, duration=(1,)) - self.assertEqual(t2.duration, timedelta(days=1)) - t3 = Todo(begin=begin, duration=timedelta(minutes=1)) - self.assertEqual(t3.duration, timedelta(minutes=1)) - - # Calculate duration from begin and due values - t4 = Todo(begin=begin, due=begin + timedelta(1)) - self.assertEqual(t4.duration, timedelta(1)) - - def test_due(self): - begin = datetime(2018, 2, 18, 12, 19, tzinfo=datetime_tzutc) - due = begin + timedelta(1) - t1 = Todo(due=due) - self.assertEqual(t1.due, begin + timedelta(1)) - - due = begin - timedelta(1) - with self.assertRaises(ValueError): - Todo(begin=begin, due=due) - - # Calculate due from begin and duration value - t2 = Todo(begin=begin, duration=(1,)) - self.assertEqual(t2.due, begin + timedelta(1)) - - def test_invalid_time_attributes(self): - # due and duration must not be set at the same time - with self.assertRaises(ValueError): - Todo(begin=datetime.now(), due=datetime.now() + timedelta(1), duration=timedelta(1)) - - # duration requires begin - with self.assertRaises(ValueError): - Todo(duration=timedelta(1)) - - def test_repr(self): - begin = datetime(2018, 2, 18, 12, 19, tzinfo=datetime_tzutc) - - t1 = Todo() - self.assertEqual(repr(t1), '') - - t2 = Todo(name='foo') - self.assertEqual(repr(t2), "") - - t3 = Todo(name='foo', begin=begin) - self.assertEqual(repr(t3), "") - - t4 = Todo(name='foo', due=begin) - self.assertEqual(repr(t4), "") - - t4 = Todo(name='foo', begin=begin, due=begin + timedelta(1)) - self.assertEqual(repr(t4), - "") - - def test_todo_lt(self): - t1 = Todo() - t2 = Todo(name='a') - t3 = Todo(name='b') - t4 = Todo(due=datetime.fromtimestamp(10)) - t5 = Todo(due=datetime.fromtimestamp(20)) - - # Check comparison by name - self.assertFalse(t1 < t1) - self.assertTrue(t1 < t2) - self.assertFalse(t2 < t1) - self.assertTrue(t2 < t3) - self.assertFalse(t3 < t2) - - # Check comparison by due time - self.assertTrue(t4 < t5) - self.assertFalse(t4 < t4) - self.assertFalse(t5 < t4) - - # Check invalid call - with self.assertRaises(TypeError): - t4 > t4.due - with self.assertRaises(TypeError): - t2 < 1 - - def test_todo_le(self): - t1 = Todo() - t2 = Todo(name='a') - t3 = Todo(name='b') - t4 = Todo(due=datetime.fromtimestamp(10)) - t5 = Todo(due=datetime.fromtimestamp(20)) - - # Check comparison by name - self.assertTrue(t1 <= t1) - self.assertTrue(t1 <= t2) - self.assertFalse(t2 <= t1) - self.assertTrue(t2 <= t3) - self.assertTrue(t2 <= t2) - self.assertFalse(t3 <= t2) - - # Check comparison by due time - self.assertTrue(t4 <= t5) - self.assertTrue(t4 <= t4) - self.assertFalse(t5 <= t4) - - # Check invalid call - with self.assertRaises(TypeError): - t4 > t4.due - with self.assertRaises(TypeError): - t2 <= 1 - - def test_todo_gt(self): - t1 = Todo() - t2 = Todo(name='a') - t3 = Todo(name='b') - t4 = Todo(due=datetime.fromtimestamp(10)) - t5 = Todo(due=datetime.fromtimestamp(20)) - - # Check comparison by name - self.assertFalse(t1 > t1) - self.assertFalse(t1 > t2) - self.assertTrue(t2 > t1) - self.assertFalse(t2 > t3) - self.assertFalse(t2 > t2) - self.assertTrue(t3 > t2) - - # Check comparison by due time - self.assertFalse(t4 > t5) - self.assertFalse(t4 > t4) - self.assertTrue(t5 > t4) - - # Check invalid call - with self.assertRaises(TypeError): - t4 > t4.due - with self.assertRaises(TypeError): - t2 > 1 - - def test_todo_ge(self): - t1 = Todo() - t2 = Todo(name='a') - t3 = Todo(name='b') - t4 = Todo(due=datetime.fromtimestamp(10)) - t5 = Todo(due=datetime.fromtimestamp(20)) - - # Check comparison by name - self.assertTrue(t1 >= t1) - self.assertTrue(t1 <= t2) - self.assertFalse(t2 <= t1) - self.assertFalse(t2 >= t3) - self.assertTrue(t2 >= t2) - self.assertTrue(t3 >= t2) - - # Check comparison by due time - self.assertFalse(t4 >= t5) - self.assertTrue(t4 >= t4) - self.assertTrue(t5 >= t4) - - # Check invalid call - with self.assertRaises(TypeError): - t4 > t4.due - with self.assertRaises(TypeError): - t2 >= 1 - - def test_todo_eq(self): - t1 = Todo() - t2 = Todo() - - self.assertTrue(t1 == t1) - self.assertFalse(t1 == t2) - - def test_todo_ne(self): - t1 = Todo() - t2 = Todo() - - self.assertFalse(t1 != t1) - self.assertTrue(t1 != t2) - - def test_extract(self): - c = Calendar(cal27) - t = next(iter(c.todos)) - self.assertEqual(t.dtstamp, dt(2018, 2, 18, 15, 47, 00, tzinfo=dateutil_tzutc)) - self.assertEqual(t.uid, 'Uid') - self.assertEqual(t.completed, dt(2018, 4, 18, 15, 00, 00, tzinfo=dateutil_tzutc)) - self.assertEqual(t.created, dt(2018, 2, 18, 15, 48, 00, tzinfo=dateutil_tzutc)) - self.assertEqual(t.description, 'Lorem ipsum dolor sit amet.') - self.assertEqual(t.begin, dt(2018, 2, 18, 16, 48, 00, tzinfo=dateutil_tzutc)) - self.assertEqual(t.location, 'Earth') - self.assertEqual(t.percent, 0) - self.assertEqual(t.priority, 0) - self.assertEqual(t.name, 'Name') - self.assertEqual(t.url, 'https://www.example.com/cal.php/todo.ics') - self.assertEqual(t.duration, timedelta(minutes=10)) - self.assertEqual(len(t.alarms), 1) - - def test_extract_due(self): - c = Calendar(cal28) - t = next(iter(c.todos)) - self.assertEqual(t.due, dt(2018, 2, 18, 16, 48, 00, tzinfo=dateutil_tzutc)) - - def test_extract_due_error_duration(self): - with self.assertRaises(ValueError): - Calendar(cal29) - - def test_extract_duration_error_due(self): - with self.assertRaises(ValueError): - Calendar(cal30) - - def test_output(self): - c = Calendar(cal27) - t = next(iter(c.todos)) - - test_str = CRLF.join(("BEGIN:VTODO", - "SEQUENCE:0", - "BEGIN:VALARM", - "ACTION:DISPLAY", - "DESCRIPTION:Event reminder", - "TRIGGER:PT1H", - "END:VALARM", - "COMPLETED:20180418T150000Z", - "CREATED:20180218T154800Z", - "DESCRIPTION:Lorem ipsum dolor sit amet.", - "DTSTAMP:20180218T154700Z", - "DURATION:PT10M", - "LOCATION:Earth", - "PERCENT-COMPLETE:0", - "PRIORITY:0", - "DTSTART:20180218T164800Z", - "SUMMARY:Name", - "UID:Uid", - "URL:https://www.example.com/cal.php/todo.ics", - "END:VTODO")) - self.assertEqual(str(t), test_str) - - def test_output_due(self): - dtstamp = datetime(2018, 2, 19, 21, 00, tzinfo=datetime_tzutc) - due = datetime(2018, 2, 20, 1, 00, tzinfo=datetime_tzutc) - t = Todo(dtstamp=dtstamp, uid='Uid', due=due) - - test_str = CRLF.join(("BEGIN:VTODO", - "DTSTAMP:20180219T210000Z", - "DUE:20180220T010000Z", - "UID:Uid", - "END:VTODO")) - self.assertEqual(str(t), test_str) - - def test_unescape_texts(self): - c = Calendar(cal31) - t = next(iter(c.todos)) - self.assertEqual(t.name, "Hello, \n World; This is a backslash : \\ and another new \n line") - self.assertEqual(t.location, "In, every text field") - self.assertEqual(t.description, "Yes, all of them;") - - def test_escape_output(self): - dtstamp = datetime(2018, 2, 19, 21, 00, tzinfo=datetime_tzutc) - t = Todo(dtstamp=dtstamp, uid='Uid') - - t.name = "Hello, with \\ special; chars and \n newlines" - t.location = "Here; too" - t.description = "Every\nwhere ! Yes, yes !" - - test_str = CRLF.join(("BEGIN:VTODO", - "DESCRIPTION:Every\\nwhere ! Yes\\, yes !", - "DTSTAMP:20180219T210000Z", - "LOCATION:Here\\; too", - "SUMMARY:Hello\\, with \\\\ special\\; chars and \\n newlines", - "UID:Uid", - "END:VTODO")) - self.assertEqual(str(t), test_str) diff --git a/tests/unfold_lines.py b/tests/unfold_lines.py deleted file mode 100644 index f56e4b97..00000000 --- a/tests/unfold_lines.py +++ /dev/null @@ -1,62 +0,0 @@ -import unittest - -from ics.grammar.parse import unfold_lines - -from .fixture import (cal1, cal2, cal3, cal6, cal7, cal8, cal9, cal26, - unfolded_cal1, unfolded_cal2, unfolded_cal6, - unfolded_cal26) - - -class TestUnfoldLines(unittest.TestCase): - - def test_no_folded_lines(self): - self.assertEqual(list(unfold_lines(cal2.split('\n'))), unfolded_cal2) - - def test_simple_folded_lines(self): - self.assertEqual(list(unfold_lines(cal1.split('\n'))), unfolded_cal1) - - def test_last_line_folded(self): - self.assertEqual(list(unfold_lines(cal6.split('\n'))), unfolded_cal6) - - def test_tabbed_folding(self): - self.assertEqual(list(unfold_lines(cal26.split('\n'))), unfolded_cal26) - - def test_simple(self): - dataset = { - 'a': ('a',), - 'ab': ('ab',), - 'a\nb': ('a', 'b',), - 'a\n b': ('ab',), - 'a \n b': ('a b',), - 'a\n b\nc': ('ab', 'c',), - 'a\nb\n c': ('a', 'bc',), - 'a\nb\nc': ('a', 'b', 'c',), - 'a\n b\n c': ('abc',), - 'a \n b \n c': ('a b c',), - } - for line in dataset: - expected = dataset[line] - got = tuple(unfold_lines(line.split('\n'))) - self.assertEqual(expected, got) - - def test_empty(self): - self.assertEqual(list(unfold_lines([])), []) - - def test_one_line(self): - self.assertEqual(list(unfold_lines(cal6.split('\n'))), unfolded_cal6) - - def test_two_lines(self): - self.assertEqual(list(unfold_lines(cal3.split('\n'))), - ['BEGIN:VCALENDAR', 'END:VCALENDAR']) - - def test_no_empty_lines(self): - self.assertEqual(list(unfold_lines(cal7.split('\n'))), - ['BEGIN:VCALENDAR', 'END:VCALENDAR']) - - def test_no_whitespace_lines(self): - self.assertEqual(list(unfold_lines(cal8.split('\n'))), - ['BEGIN:VCALENDAR', 'END:VCALENDAR']) - - def test_first_line_empty(self): - self.assertEqual(list(unfold_lines(cal9.split('\n'))), - ['BEGIN:VCALENDAR', 'END:VCALENDAR']) diff --git a/tests/utils.py b/tests/utils.py deleted file mode 100644 index 0abf00f9..00000000 --- a/tests/utils.py +++ /dev/null @@ -1,89 +0,0 @@ -import unittest -from datetime import timedelta - -from ics.grammar.parse import ParseError, string_to_container -from ics.utils import (parse_datetime, parse_duration, remove_x, - serialize_duration) -from tests.fixture import cal1, cal2 - - -class TestParseDuration(unittest.TestCase): - dataset_simple = { - 'P1W': (7, 0), 'P1D': (1, 0), '-P1D': (-1, 0), - 'P1H': (0, 3600), 'P1M': (0, 60), 'P1S': (0, 1), - 'PT1H': (0, 3600), 'PT1M': (0, 60), 'PT1S': (0, 1), - 'PT': (0, 0) - } - - dataset_combined = { - "P1D1WT1H": (8, 3600), "P1DT1H1W": (8, 3600), "P1DT1H1M1W": (8, 3660), - "P1DT1H1M1S1W": (8, 3661), "P1DT1H": (1, 3600), "P1DT1H1M": (1, 3660), - "PT1S1M": (0, 61) - } - - def run_on_dataset(self, dataset): - for test in dataset: - expected = dataset[test] - self.assertEqual(parse_duration(test), timedelta(*expected)) - - def test_simple(self): - self.run_on_dataset(self.dataset_simple) - - def test_combined(self): - self.run_on_dataset(self.dataset_combined) - - def test_no_p(self): - self.assertRaises(ParseError, parse_duration, 'caca') - - def test_two_letters(self): - self.assertRaises(ParseError, parse_duration, 'P1DF') - - def test_two_occurences(self): - self.assertRaises(ParseError, parse_duration, 'P1D1D') - - -class TestTimedeltaToDuration(unittest.TestCase): - dataset_simple = { - # (0, 0): 'P', - (0, 0): 'PT0S', - (0, 1): 'PT1S', (0, 60): 'PT1M', (0, 3600): 'PT1H', - (1, 0): 'P1D', (7, 0): 'P7D', # (7, 0): 'P1W', - } - - dataset_combined = { - (1, 1): 'P1DT1S', - # (8, 3661): 'P1W1DT1H1M1S', (15, 18020): 'P2W1DT5H20S', - (8, 3661): 'P8DT1H1M1S', (15, 18020): 'P15DT5H20S', - } - - def run_on_dataset(self, dataset): - for test in dataset: - expected = dataset[test] - self.assertEqual(serialize_duration(timedelta(*test)), expected) - - def test_simple(self): - self.run_on_dataset(self.dataset_simple) - - def test_combined(self): - self.run_on_dataset(self.dataset_combined) - - -class TestRemoveX(unittest.TestCase): - - def test_with_x(self): - c = string_to_container(cal1)[0] - remove_x(c) - for line in c: - self.assertFalse(line.name.startswith('X-')) - - def test_without_x(self): - c = string_to_container(cal2)[0] - c2 = string_to_container(cal2)[0] - remove_x(c) - self.assertSequenceEqual(c, c2) - - -class Test_parse_datetime(unittest.TestCase): - - def test_none(self): - self.assertIs(None, parse_datetime(None)) diff --git a/tests/valuetype/__init__.py b/tests/valuetype/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/valuetype/text.py b/tests/valuetype/text.py new file mode 100644 index 00000000..36797afe --- /dev/null +++ b/tests/valuetype/text.py @@ -0,0 +1,80 @@ +import attr +import pytest +from hypothesis import given + +from ics.grammar import ContentLine, string_to_container +from ics.valuetype.text import TextConverter +# Text may be comma-separated multi-value but is never quoted, with the characters [\\;,\n] escaped +from tests.grammar import VALUE + +TextConv: TextConverter = TextConverter.INST + + +@pytest.mark.parametrize("inp_esc, out_uesc", [ + ( + "SUMMARY:Project XYZ Final Review\\nConference Room - 3B\\nCome Prepared.", + ContentLine("SUMMARY", value="Project XYZ Final Review\nConference Room - 3B\nCome Prepared.") + ), + ( + "DESCRIPTION;ALTREP=\"cid:part1.0001@example.org\":The Fall'98 Wild Wizards Conference - - Las Vegas\\, NV\\, USA", + ContentLine("DESCRIPTION", {"ALTREP": ["cid:part1.0001@example.org"]}, value="The Fall'98 Wild Wizards Conference - - Las Vegas, NV, USA") + ), + ( + "TEST:abc\\r\\n\\,\\;:\"\t=xyz", + ContentLine("TEST", value="abc\r\n,;:\"\t=xyz") + ), +]) +def test_example_text_recode(inp_esc, out_uesc): + par_esc = ContentLine.parse(inp_esc) + par_uesc = attr.evolve(par_esc, value=TextConv.parse(par_esc.value)) + out_esc = attr.evolve(out_uesc, value=TextConv.serialize(out_uesc.value)) + assert par_uesc == out_uesc + ser_esc = out_esc.serialize() + assert inp_esc == ser_esc + assert string_to_container(inp_esc) == [par_esc] + + +# TODO list examples ("RESOURCES:EASEL,PROJECTOR,VCR", ContentLine("RESOURCES", value="EASEL,PROJECTOR,VCR")) + +def test_trailing_escape_text(): + with pytest.raises(ValueError) as excinfo: + TextConv.parse("text\\,with\tdangling escape\\") + assert "not end with an escape sequence" in str(excinfo.value) + + assert TextConv.parse("text\\,with\tdangling escape") == "text,with\tdangling escape" + assert TextConv.serialize("text,text\\,with\tdangling escape\\") == "text\\,text\\\\\\,with\tdangling escape\\\\" + + +def test_broken_escape(): + with pytest.raises(ValueError) as e: + TextConv.unescape_text("\\t") + assert e.match("can't handle escaped character") + with pytest.raises(ValueError) as e: + TextConv.unescape_text("abc;def") + assert e.match("unescaped character") + +def test_trailing_escape_value_list(): + cl1 = ContentLine.parse("TEST:this is,a list \\, with a\\\\,trailing escape\\") + with pytest.raises(ValueError) as excinfo: + list(TextConv.split_value_list(cl1.value)) + assert "not end with an escape sequence" in str(excinfo.value) + + cl2 = ContentLine.parse("TEST:this is,a list \\, with a\\\\,trailing escape") + assert list(TextConv.split_value_list(cl2.value)) == \ + ["this is", "a list \\, with a\\\\", "trailing escape"] + assert [TextConv.parse(v) for v in TextConv.split_value_list(cl2.value)] == \ + ["this is", "a list , with a\\", "trailing escape"] + + +@given(value=VALUE) +def test_any_text_value_recode(value): + esc = TextConv.serialize(value) + assert TextConv.parse(esc) == value + cl = ContentLine("TEST", value=esc) + assert ContentLine.parse(cl.serialize()) == cl + assert string_to_container(cl.serialize()) == [cl] + vals = [esc, esc, "test", esc] + cl2 = ContentLine("TEST", value=TextConv.join_value_list(vals)) + assert list(TextConv.split_value_list(cl2.value)) == vals + assert ContentLine.parse(cl.serialize()) == cl + assert string_to_container(cl.serialize()) == [cl] diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..058017db --- /dev/null +++ b/tox.ini @@ -0,0 +1,95 @@ +[tox] +isolated_build = true +envlist = py36, py37, py38, pypy3, checks, docs + +[testenv] +description = Run the pytest suite +setenv = + PYTHONDEVMODE=1 +extras = + test +# see pyproject.toml for the list of dependencies in the "test" extra +commands = + pytest -V + python -c 'import sys, pkg_resources; dist = pkg_resources.get_distribution("ics"); print(repr(dist), dist.__dict__, sys.path)' + pytest --basetemp="{envtmpdir}" {posargs} + +[testenv:flake8] +description = Run the flake8 code style checks +deps = flake8>=3.8.1 +commands = + flake8 --version + flake8 src/ + +[testenv:mypy] +description = Run the mypy type checks +deps = mypy>=0.770 +commands = + mypy -V + mypy --config-file=tox.ini src/ + +[testenv:checks] +description = Run all code checkers (flake8 and mypy) +deps = + {[testenv:flake8]deps} + {[testenv:mypy]deps} +commands = + {[testenv:flake8]commands} + {[testenv:mypy]commands} + +[testenv:docs] +description = Build the documentation with sphinx +deps = + sphinx + sphinxcontrib-napoleon + sphinx-autodoc-typehints +commands = + sphinx-build -d "{toxworkdir}/docs_doctree" doc "{toxworkdir}/docs_out" --color -bhtml {posargs} + python -c 'import pathlib; print("documentation available under file://\{0\}".format(pathlib.Path(r"{toxworkdir}") / "docs_out" / "index.html"))' + +[gh-actions] +python = + 3.6: py36 + 3.7: py37 + 3.8: py38, docs, flake8, mypy + +[pytest] +python_files = *.py +norecursedirs = dist venv .git .hypothesis .mypy_cache .pytest_cache .tox .eggs .cache ics.egg-info +testpaths = doc tests +addopts = + --doctest-glob='*.rst' --doctest-modules + --ignore doc/conf.py + --hypothesis-show-statistics + --cov=ics --cov-report=xml + -s + +[flake8] +# http://flake8.pycqa.org/en/latest/user/error-codes.html +# https://pycodestyle.readthedocs.io/en/latest/intro.html#error-codes +ignore = +# E127 continuation line over-indented for visual indent + E127 +# E128 continuation line under-indented for visual indent + E128 +# E251 unexpected spaces around keyword / parameter equals + E251 +# E402 module level import not at top of file + E402 +# E501 line too long (82 > 79 characters) + E501 +# E701 multiple statements on one line (colon) + E701 +# E704 multiple statements on one line (def) + E704 +# E731 do not assign a lambda expression, use a def + E731 +# F401 module imported but unused + F401 +# F403 ‘from module import *’ used; unable to detect undefined names + F403 + +[mypy] +python_version = 3.8 +warn_unused_configs = True +show_error_codes = True