diff --git a/.gitignore b/.gitignore
index 33d387b5..111ab755 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,7 +19,6 @@
__pycache__/
# Ignore new .msg files added from testing
-
/example-msg-files/expected-outputs/
/example-msg-files/*.msg
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0145e45d..358eca51 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,59 @@
+**v0.42.0**
+* [[TeamMsgExtractor #372](https://github.com/TeamMsgExtractor/msg-extractor/issues/372)] Changed the way that the save functions return a value. This makes the return value from all save functions much more informative, allowing a user to separate if a fole or folder (or if more than one) was saved from the function. It also guarentees that all classes from this module will return the relevent path(s) if data is actually saved.
+* [[TeamMsgExtractor #288](https://github.com/TeamMsgExtractor/msg-extractor/issues/288)] Added feature to allow attachment save functions to simply overwrite existing files of the same name. This can be done with the `overwriteExisting` keyword argument from code or the `--overwrite-existing` option from the command line.
+* [[TeamMsgExtractor #40](https://github.com/TeamMsgExtractor/msg-extractor/issues/40)] Added new submodule `custom_attachments`. This submodule provides an extendable way to handle custom attachment types, attachment types whose structure and formatting are not defined in the Microsoft documentation for MSG files. This includes a handler to at least partially cover support for Outlook images.
+* [[TeamMsgExtractor #373](https://github.com/TeamMsgExtractor/msg-extractor/issues/373)] Added the `encoding` submodule for encoding tasks, including proper support for Microsoft's implementation of CP950. This gets added to the codecs list as "windows-950".
+ * Added infrastructure to make it easy to add variable-byte (up to two bytes) encodings and single-byte encodings.
+ * Added the following encodings:
+ * windows-874
+ * x-mac-ce
+ * x-mac-cyrillic
+ * x-mac-greek
+ * x-mac-icelandic
+ * x-mac-turkish
+* Fixed an issue in the save functions that left the possibility for the zip files to not end up closing if the save function created it and then had an exception.
+* Added new property `AttachmentBase.clsid` which returns the listed CLSID value of the data stream/storage of the attachment.
+* Changed internal behavior of `MSGFile.attachments`. This should not cause any noticeable changes to the output.
+* Refactored code significantly to make it more organized.
+* Changed the exports from the main module to only include an important subset of the module. For other items, you'll have to import the submodule that it falls under to access it. Submodules export all important pieces, so it will be easier to find.
+ * This includes having many modules be under entirely new paths. Some of these changes have been done with no deprecation, something I generally try to avoid. This is happening at the same time as the public api is significantly changing, which makes it more acceptable.
+* Fixed `__main__` using the wrong enum for error behavior.
+* Fixed `Named.get` being severely out of date (it's not used anywhere by the module which is why it wasn't noticed).
+* Fixed `Named.__getitem__` being entirely case-sensitive.
+* Switched much of the internal code (and the `treePath` property of all classes that have it) to using `weakref.ReferenceType` to avoid hard cyclic references.
+* Fixed `Recipient._getTypedStream` never returning a value.
+* Added additional type hints in various places.
+* Modified tests.py to only run if it is run as a file instead of imported.
+* Changed `knownMsgClass` to a private function since it is explicitly not being exported by any part of the module.
+* Removed unusued function `getFullClassName`.
+* Fixes to the HTML body when saving as HTML will no longer require the `preparedHtml`/`--prepared-html` option.
+* Removed unused exceptions.
+* Entirely reoganized the way attachments are initialized, including the class that will be used in various circumstances. Embedded MSG files, custom attachments, and web attachments will all use dedicated classes that are subclasses of AttachmentBase.
+ * With this change, the way to specify a new Attachment class is to override the function used when creating attachments. This can be done by passing `attachmentInit = myFunction` as an option to `openMsg`. This function MUST return an instance of AttachmentBase.
+* Added first implementation of web attachments. Saving is not currently possible, but basic relevent property access is now possible. Saving will not be stopped by this attachment if `skipNotImplemented = True` is passed to the save function.
+* Changed the option to suppress `RTFDE` errors to fall under the `ErrorBehavior` enum. Usage of the original option will be allowable, but is being marked as deprecated. However, it is still a dedicated option from the command line.
+ * Also fixed the option not properly ignoring some RTFDE errors, specifically the ones that it is normal for the module to throw.
+* Removed some constants that are not used by the module.
+* Updated to support `RTFDE` version `0.1.0`. Users encountering random erros from that module should find that those errors have disappeared. If you get errors from it still, bring up the issue on their GitHub.
+* Fixed bug that would cause weird behavior if you gave an empty string as the path for an MSG file.
+* Added support for `IPM.StickyNote`.
+* Fixed an issue that would cause MSG file to never close if an error happened during any of the `__init__` functions for MSG classes.
+* Removed unneeded `chardet` dependency.
+* Removed `Contact.__init__` as it didn't provide any unique behavior.
+* Changed the documentation of `openMsg` to specify that it accepts all options recognized by MSGFile subclasses, allowing the doc string to not be modified every time one of them is changed.
+ * Changed the documentaion of various `__init__` methods to do the same thing.
+* Added `dataType` property to `AttachmentBase` and `SignedAttachment` for checking the class that the data will be, if accessible. Returns `None` if the data is inaccessible, including because accessing it would throw an exception.
+* Added new enum `InsecureFeatures` and option `insecureFeatures`. This option will allow certain features with security implcations to be used for files that you trust. Currently the only feature it supports is the usage of `PIL`/`Pillow` to open and modify images. All features like this will be opt-in to reduce possible vulnerabilities.
+* Modified all custom exceptions the module uses to derive from a single base class for better organization.
+ * Added new exceptions to handle some of the situations previously handled by base Python exceptions.
+* Changed internal handling of the `prefix` option for `MSGFile.__init__` (and therefore `openMsg`). If you are not setting this manually, you should notice little difference.
+* Made enums less strict and converted all using `fromBits` to be `IntFlag` enums.
+* Fixed `CalendarBase.keywords` being blatantly incorrect (it was so bad I don't know how it slipped through).
+* Fixed `Contact.gender` being blatantly incorrect.
+* Fixed sender not being properly decoded in some circumstances.
+* Changed behavior of `MSGFile` to have olefile raise defects of type `DEFECT_INCORRECT` and above instead of just `DEFECT_FATAL`. Uncaught issues of `DEFECT_INCORRECT` can often cause the module to have parsing issues that may be misleading, this just ensures the issue is clarified. This behavior can be reverted back to the previous with `ErrorBehavior.OLE_DEFECT_INCORRECT`.
+* Fixed potential issues that may have made is possible for certain attachments to ignore filename conflict resolution code.
+
**v0.41.5**
* Fixed an issue from version `0.41.3` where the header being present but missing the `From` field would cause an exception.
diff --git a/README.rst b/README.rst
index 4020af6b..647e6761 100644
--- a/README.rst
+++ b/README.rst
@@ -59,62 +59,54 @@ Currently, the README is in the process of being redone. For now, please
refer to the usage information provided from the program's help dialog:
::
- usage: extract_msg [-h] [--use-content-id] [--validate] [--json] [--file-logging] [-v] [--log LOG] [--config CONFIGPATH]
- [--out OUTPATH] [--use-filename] [--dump-stdout] [--html] [--pdf] [--wk-path WKPATH]
- [--wk-options [WKOPTIONS ...]] [--prepared-html] [--charset CHARSET] [--raw] [--rtf] [--allow-fallback]
- [--skip-body-not-found] [--zip ZIP] [--save-header] [--attachments-only] [--skip-hidden] [--no-folders]
- [--skip-embedded] [--extract-embedded] [--skip-not-implemented] [--out-name OUTNAME | --glob] [--ignore-rtfde]
- [--progress]
- msg [msg ...]
-
- extract_msg: Extracts emails and attachments saved in Microsoft Outlook's .msg files. https://github.com/TeamMsgExtractor/msg-
- extractor
-
- positional arguments:
- msg An MSG file to be parsed.
-
- optional arguments:
- -h, --help show this help message and exit
- --use-content-id, --cid
- Save attachments by their Content ID, if they have one. Useful when working with the HTML body.
- --validate Turns on file validation mode. Turns off regular file output.
- --json Changes to write output files as json.
- --file-logging Enables file logging. Implies --verbose level 1.
- -v, --verbose Turns on console logging. Specify more than once for higher verbosity.
- --log LOG Set the path to write the file log to.
- --config CONFIGPATH Set the path to load the logging config from.
- --out OUTPATH Set the folder to use for the program output. (Default: Current directory)
- --use-filename Sets whether the name of each output is based on the msg filename.
- --dump-stdout Tells the program to dump the message body (plain text) to stdout. Overrides saving arguments.
- --html Sets whether the output should be HTML. If this is not possible, will error.
- --pdf Saves the body as a PDF. If this is not possible, will error.
- --wk-path WKPATH Overrides the path for finding wkhtmltopdf.
- --wk-options [WKOPTIONS ...]
- Sets additional options to be used in wkhtmltopdf. Should be a series of options and values, replacing the -
- or -- in the beginning with + or ++, respectively. For example: --wk-options "+O Landscape"
- --prepared-html When used in conjunction with --html, sets whether the HTML output should be prepared for embedded
- attachments.
- --charset CHARSET Character set to use for the prepared HTML in the added tag. (Default: utf-8)
- --raw Sets whether the output should be raw. If this is not possible, will error.
- --rtf Sets whether the output should be RTF. If this is not possible, will error.
- --allow-fallback Tells the program to fallback to a different save type if the selected one is not possible.
- --skip-body-not-found
- Skips saving the body if the body cannot be found, rather than throwing an error.
- --zip ZIP Path to use for saving to a zip file.
- --save-header Store the header in a separate file.
- --attachments-only Specify to only save attachments from an msg file.
- --skip-hidden Skips any attachment marked as hidden (usually ones embedded in the body).
- --no-folders Stores everything in the location specified by --out. Requires --attachments-only and is incompatible with
- --out-name.
- --skip-embedded Skips all embedded MSG files when saving attachments.
- --extract-embedded Extracts the embedded MSG files as MSG files instead of running their save functions.
- --skip-not-implemented, --skip-ni
- Skips any attachments that are not implemented, allowing saving of the rest of the message.
- --out-name OUTNAME Name to be used with saving the file output. Cannot be used if you are saving more than one file.
- --glob, --wildcard Interpret all paths as having wildcards. Incompatible with --out-name.
- --ignore-rtfde Ignores all errors thrown from RTFDE when trying to save. Useful for allowing fallback to continue when an
- exception happens.
- --progress Shows what file the program is currently working on during it's progress.
+ usage: extract_msg [-h] [--use-content-id] [--json] [--file-logging] [-v] [--log LOG] [--config CONFIGPATH] [--out OUTPATH] [--use-filename] [--dump-stdout] [--html] [--pdf] [--wk-path WKPATH] [--wk-options [WKOPTIONS ...]]
+ [--prepared-html] [--charset CHARSET] [--raw] [--rtf] [--allow-fallback] [--skip-body-not-found] [--zip ZIP] [--save-header] [--attachments-only] [--skip-hidden] [--no-folders] [--skip-embedded] [--extract-embedded]
+ [--overwrite-existing] [--skip-not-implemented] [--out-name OUTNAME | --glob] [--ignore-rtfde] [--progress]
+ msg [msg ...]
+
+ extract_msg: Extracts emails and attachments saved in Microsoft Outlook's .msg files. https://github.com/TeamMsgExtractor/msg-extractor
+
+ positional arguments:
+ msg An MSG file to be parsed.
+
+ options:
+ -h, --help show this help message and exit
+ --use-content-id, --cid
+ Save attachments by their Content ID, if they have one. Useful when working with the HTML body.
+ --json Changes to write output files as json.
+ --file-logging Enables file logging. Implies --verbose level 1.
+ -v, --verbose Turns on console logging. Specify more than once for higher verbosity.
+ --log LOG Set the path to write the file log to.
+ --config CONFIGPATH Set the path to load the logging config from.
+ --out OUTPATH Set the folder to use for the program output. (Default: Current directory)
+ --use-filename Sets whether the name of each output is based on the msg filename.
+ --dump-stdout Tells the program to dump the message body (plain text) to stdout. Overrides saving arguments.
+ --html Sets whether the output should be HTML. If this is not possible, will error.
+ --pdf Saves the body as a PDF. If this is not possible, will error.
+ --wk-path WKPATH Overrides the path for finding wkhtmltopdf.
+ --wk-options [WKOPTIONS ...]
+ Sets additional options to be used in wkhtmltopdf. Should be a series of options and values, replacing the - or -- in the beginning with + or ++, respectively. For example: --wk-options "+O Landscape"
+ --prepared-html When used in conjunction with --html, sets whether the HTML output should be prepared for embedded attachments.
+ --charset CHARSET Character set to use for the prepared HTML in the added tag. (Default: utf-8)
+ --raw Sets whether the output should be raw. If this is not possible, will error.
+ --rtf Sets whether the output should be RTF. If this is not possible, will error.
+ --allow-fallback Tells the program to fallback to a different save type if the selected one is not possible.
+ --skip-body-not-found
+ Skips saving the body if the body cannot be found, rather than throwing an error.
+ --zip ZIP Path to use for saving to a zip file.
+ --save-header Store the header in a separate file.
+ --attachments-only Specify to only save attachments from an msg file.
+ --skip-hidden Skips any attachment marked as hidden (usually ones embedded in the body).
+ --no-folders Stores everything in the location specified by --out. Requires --attachments-only and is incompatible with --out-name.
+ --skip-embedded Skips all embedded MSG files when saving attachments.
+ --extract-embedded Extracts the embedded MSG files as MSG files instead of running their save functions.
+ --overwrite-existing Disables filename conflict resolution code for attachments when saving a file, causing files to be overwriten if two attachments with the same filename are on an MSG file.
+ --skip-not-implemented, --skip-ni
+ Skips any attachments that are not implemented, allowing saving of the rest of the message.
+ --out-name OUTNAME Name to be used with saving the file output. Cannot be used if you are saving more than one file.
+ --glob, --wildcard Interpret all paths as having wildcards. Incompatible with --out-name.
+ --ignore-rtfde Ignores all errors thrown from RTFDE when trying to save. Useful for allowing fallback to continue when an exception happens.
+ --progress Shows what file the program is currently working on during it's progress.
**To use this in your own script**, start by using:
@@ -250,8 +242,8 @@ your access to the newest major version of extract-msg.
.. |License: GPL v3| image:: https://img.shields.io/badge/License-GPLv3-blue.svg
:target: LICENSE.txt
-.. |PyPI3| image:: https://img.shields.io/badge/pypi-0.41.5-blue.svg
- :target: https://pypi.org/project/extract-msg/0.41.5/
+.. |PyPI3| image:: https://img.shields.io/badge/pypi-0.42.0-blue.svg
+ :target: https://pypi.org/project/extract-msg/0.42.0/
.. |PyPI2| image:: https://img.shields.io/badge/python-3.8+-brightgreen.svg
:target: https://www.python.org/downloads/release/python-3816/
diff --git a/extract_msg/__init__.py b/extract_msg/__init__.py
index 088987ac..3b964b71 100644
--- a/extract_msg/__init__.py
+++ b/extract_msg/__init__.py
@@ -27,53 +27,42 @@
# along with this program. If not, see .
__author__ = 'Destiny Peterson & Matthew Walker'
-__date__ = '2023-06-11'
-__version__ = '0.41.5'
+__date__ = '2023-07-29'
+__version__ = '0.42.0'
__all__ = [
# Modules:
- 'constants',
+ 'attachments',
'enums',
'exceptions',
+ 'msg_classes',
+ 'properties',
# Classes:
- 'AppointmentMeeting',
'Attachment',
- 'Contact',
- 'MeetingForwardNotification',
- 'MeetingRequest',
- 'MeetingResponse',
+ 'AttachmentBase',
'Message',
- 'MessageBase',
- 'MessageSigned',
- 'MessageSignedBase',
'MSGFile',
- 'Post',
- 'Properties',
+ 'Named',
+ 'NamedProperties',
+ 'OleWriter',
+ 'PropertiesStore',
'Recipient',
- 'Task',
+ 'SignedAttachment',
- #Functions:
- 'createProp',
+ # Functions:
'openMsg',
'openMsgBulk',
]
+
+# Ensure these are imported before anything else.
from . import constants, enums, exceptions
-from .appointment import AppointmentMeeting
-from .attachment import Attachment
-from .contact import Contact
-from .meeting_forward import MeetingForwardNotification
-from .meeting_request import MeetingRequest
-from .meeting_response import MeetingResponse
-from .message import Message
-from .message_base import MessageBase
-from .message_signed import MessageSigned
-from .message_signed_base import MessageSignedBase
-from .msg import MSGFile
-from .post import Post
-from .prop import createProp
-from .properties import Properties
-from .recipient import Recipient
-from .task import Task
-from .utils import openMsg, openMsgBulk
+
+from . import attachments, msg_classes, properties
+from .attachments import Attachment, AttachmentBase, SignedAttachment
+from .msg_classes import Message, MSGFile
+from .ole_writer import OleWriter
+from .open_msg import openMsg, openMsgBulk
+from .properties import Named, NamedProperties, PropertiesStore
+from .recipient import Recipient
\ No newline at end of file
diff --git a/extract_msg/__main__.py b/extract_msg/__main__.py
index 4af7ed3c..78055ff5 100644
--- a/extract_msg/__main__.py
+++ b/extract_msg/__main__.py
@@ -2,13 +2,14 @@
'main',
]
+
import os
import sys
import traceback
import zipfile
-from extract_msg import __doc__, utils
-from extract_msg.enums import AttachErrorBehavior
+from extract_msg import __doc__, openMsg, utils
+from extract_msg.enums import ErrorBehavior
def main() -> None:
@@ -49,6 +50,7 @@ def main() -> None:
'extractEmbedded': args.extractEmbedded,
'html': args.html,
'json': args.json,
+ 'overwriteExisting': args.overwriteExisting,
'pdf': args.pdf,
'preparedHtml': args.preparedHtml,
'rtf': args.rtf,
@@ -64,13 +66,13 @@ def main() -> None:
}
openKwargs = {
- 'ignoreRtfDeErrors': args.ignoreRtfDeErrors,
+ 'errorBehavior': ErrorBehavior.RTFDE if args.ignoreRtfDeErrors else ErrorBehavior.THROW,
}
# If we are skipping the NotImplementedError attachments, we need to
# suppress the error.
if args.skipNotImplemented:
- openKwargs['attachmentErrorBehavior'] = AttachErrorBehavior.NOT_IMPLEMENTED
+ openKwargs['errorBehavior'] |= ErrorBehavior.ATTACH_NOT_IMPLEMENTED
def strSanitize(inp):
"""
@@ -91,7 +93,7 @@ def strSanitize(inp):
except UnicodeEncodeError:
print(f'Saving file "{strSanitize(x)}" (failed to print without repr)...')
try:
- with utils.openMsg(x, **openKwargs) as msg:
+ with openMsg(x, **openKwargs) as msg:
if args.dumpStdout:
print(msg.body)
elif args.noFolders:
diff --git a/extract_msg/attachment.py b/extract_msg/attachment.py
deleted file mode 100644
index 0a387fe3..00000000
--- a/extract_msg/attachment.py
+++ /dev/null
@@ -1,331 +0,0 @@
-from __future__ import annotations
-
-
-__all__ = [
- 'Attachment',
- 'BrokenAttachment',
- 'UnsupportedAttachment',
-]
-
-
-import logging
-import os
-import pathlib
-import random
-import string
-import zipfile
-
-from typing import Optional, TYPE_CHECKING, Union
-
-from . import constants
-from .attachment_base import AttachmentBase
-from .enums import AttachmentType
-from .exceptions import StandardViolationError
-from .utils import createZipOpen, inputToString, openMsg, prepareFilename
-
-
-# Allow for nice type checking.
-if TYPE_CHECKING:
- from .msg import MSGFile
-
-logger = logging.getLogger(__name__)
-logger.addHandler(logging.NullHandler())
-
-
-class Attachment(AttachmentBase):
- """
- Stores the attachment data of a Message instance.
- Should the attachment be an embeded message, the
- class used to create it will be the same as the
- Message class used to create the attachment.
- """
-
- def __init__(self, msg, dir_):
- """
- :param msg: the Message instance that the attachment belongs to.
- :param dir_: the directory inside the msg file where the attachment is
- located.
- """
- super().__init__(msg, dir_)
-
- if '37050003' not in self.props:
- from .prop import createProp
-
- logger.warning(f'Attachment method property not found on attachment {dir_}. Code will attempt to guess the type.')
- logger.log(5, self.props)
-
- # Because this condition is actually kind of a violation of the
- # standard, we are just going to do this in a dumb way. Basically we
- # are going to try to set the attach method *manually* just so I
- # don't have to go and modify the following code.
- if self.exists('__substg1.0_37010102'):
- # Set it as data and call it a day.
- propData = b'\x03\x00\x057\x07\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00'
- elif self.exists('__substg1.0_3701000D'):
- # If it is a folder and we have properties, call it an MSG file.
- if self.exists('__substg1.0_3701000D/__properties_version1.0'):
- propData = b'\x03\x00\x057\x07\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00'
- else:
- # Call if custom attachment data.
- propData = b'\x03\x00\x057\x07\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00'
- else:
- # Can't autodetect it, so throw an error.
- raise StandardViolationError(f'Attachment method missing on attachment {dir_}, and it could not be determined automatically.')
-
- self.props._propDict['37050003'] = createProp(propData)
-
- # Get attachment data.
- if self.exists('__substg1.0_37010102'):
- self.__type = AttachmentType.DATA
- self.__data = self._getStream('__substg1.0_37010102')
- elif self.exists('__substg1.0_3701000D'):
- if (self.props['37050003'].value & 0x7) != 0x5:
- raise NotImplementedError(
- 'Current version of extract_msg does not support extraction of containers that are not embedded msg files.')
- # TODO add implementation.
- else:
- self.__prefix = msg.prefixList + [dir_, '__substg1.0_3701000D']
- self.__type = AttachmentType.MSG
- self.__data = openMsg(self.msg.path, prefix = self.__prefix, parentMsg = self.msg, treePath = self.treePath, **self.msg.kwargs)
- elif (self.props['37050003'].value & 0x7) == 0x7:
- # TODO Handling for special attacment type 0x7.
- self.__type = AttachmentType.WEB
- raise NotImplementedError('Attachments of type afByWebReference are not currently supported.')
- else:
- raise TypeError('Unknown attachment type.')
-
- def getFilename(self, **kwargs) -> str:
- """
- Returns the filename to use for the attachment.
-
- :param contentId: Use the contentId, if available.
- :param customFilename: A custom name to use for the file.
-
- If the filename starts with "UnknownFilename" then there is no guarentee
- that the files will have exactly the same filename.
- """
- filename = None
- customFilename = kwargs.get('customFilename')
- if customFilename:
- customFilename = str(customFilename)
- # First we need to validate it. If there are invalid characters,
- # this will detect it.
- if constants.RE_INVALID_FILENAME_CHARACTERS.search(customFilename):
- raise ValueError('Invalid character found in customFilename. Must not contain any of the following characters: \\/:*?"<>|')
- filename = customFilename
- else:
- # If not...
- # Check if user wants to save the file under the Content-ID.
- if kwargs.get('contentId', False):
- filename = self.cid
- # If filename is None at this point, use long filename as first
- # preference.
- if not filename:
- filename = self.name
- # Otherwise just make something up!
- if not filename:
- return self.randomFilename
-
- return filename
-
- def regenerateRandomName(self) -> str:
- """
- Used to regenerate the random filename used if the attachment cannot
- find a usable filename.
- """
- self.__randomName = inputToString('UnknownFilename ' + \
- ''.join(random.choice(string.ascii_uppercase + string.digits)
- for _ in range(5)) + '.bin', 'ascii')
-
- def save(self, **kwargs) -> Optional[Union[str, MSGFile]]:
- """
- Saves the attachment data.
-
- The name of the file is determined by several factors. The first
- thing that is checked is if you have provided :param customFilename:
- to this function. If you have, that is the name that will be used.
- If no custom name has been provided and :param contentId: is True,
- the file will be saved using the content ID of the attachment. If
- it is not found or :param contentId: is False, the long filename
- will be used. If the long filename is not found, the short one will
- be used. If after all of this a usable filename has not been found, a
- random one will be used (accessible from `Attachment.randomFilename`).
- After the name to use has been determined, it will then be shortened to
- make sure that it is not more than the value of :param maxNameLength:.
-
- To change the directory that the attachment is saved to, set the value
- of :param customPath: when calling this function. The default save
- directory is the working directory.
-
- If you want to save the contents into a ZipFile or similar object,
- either pass a path to where you want to create one or pass an instance
- to :param zip:. If :param zip: is an instance, :param customPath: will
- refer to a location inside the zip file.
-
- :param extractEmbedded: If True, causes the attachment, should it be an
- embedded MSG file, to save as a .msg file instead of calling it's
- save function.
- :param skipEmbedded: If True, skips saving this attachment if it is an
- embedded MSG file.
- """
- # First check if we are skipping embedded messages and stop
- # *immediately* if we are.
- if self.type is AttachmentType.MSG and kwargs.get('skipEmbedded'):
- return None
-
- # Get the filename to use.
- filename = self.getFilename(**kwargs)
-
- # Someone managed to have a null character here, so let's get rid of that
- filename = prepareFilename(inputToString(filename, self.msg.stringEncoding))
-
- # Get the maximum name length.
- maxNameLength = kwargs.get('maxNameLength', 256)
-
- # Make sure the filename is not longer than it should be.
- if len(filename) > maxNameLength:
- name, ext = os.path.splitext(filename)
- filename = name[:maxNameLength - len(ext)] + ext
-
- # Check if we are doing a zip file.
- _zip = kwargs.get('zip')
-
- # ZipFile handling.
- if _zip:
- # If we are doing a zip file, first check that we have been given a path.
- if isinstance(_zip, (str, pathlib.Path)):
- # If we have a path then we use the zip file.
- _zip = zipfile.ZipFile(_zip, 'a', zipfile.ZIP_DEFLATED)
- kwargs['zip'] = _zip
- createdZip = True
- else:
- createdZip = False
- # Path needs to be done in a special way if we are in a zip file.
- customPath = pathlib.Path(kwargs.get('customPath', ''))
- # Set the open command to be that of the zip file.
- _open = createZipOpen(_zip.open)
- # Zip files use w for writing in binary.
- mode = 'w'
- else:
- customPath = pathlib.Path(kwargs.get('customPath', '.')).absolute()
- mode = 'wb'
- _open = open
-
- fullFilename = customPath / filename
-
- if self.type is AttachmentType.DATA:
- if _zip:
- name, ext = os.path.splitext(filename)
- nameList = _zip.namelist()
- if str(fullFilename).replace('\\', '/') in nameList:
- for i in range(2, 100):
- testName = customPath / f'{name} ({i}){ext}'
- if str(testName).replace('\\', '/') not in nameList:
- fullFilename = testName
- break
- else:
- # If we couldn't find one that didn't exist.
- raise FileExistsError(f'Could not create the specified file because it already exists ("{fullFilename}").')
- else:
- if fullFilename.exists():
- # Try to split the filename into a name and extention.
- name, ext = os.path.splitext(filename)
- # Try to add a number to it so that we can save without overwriting.
- for i in range(2, 100):
- testName = customPath / f'{name} ({i}){ext}'
- if not testName.exists():
- fullFilename = testName
- break
- else:
- # If we couldn't find one that didn't exist.
- raise FileExistsError(f'Could not create the specified file because it already exists ("{fullFilename}").')
-
- with _open(str(fullFilename), mode) as f:
- f.write(self.__data)
-
- # Close the ZipFile if this function created it.
- if _zip and createdZip:
- _zip.close()
-
- return str(fullFilename)
- else:
- if kwargs.get('extractEmbedded', False):
- with _open(str(fullFilename), mode) as f:
- self.data.export(f)
- else:
- self.saveEmbededMessage(**kwargs)
-
- # Close the ZipFile if this function created it.
- if _zip and createdZip:
- _zip.close()
-
- return self.msg
-
- def saveEmbededMessage(self, **kwargs) -> None:
- """
- Seperate function from save to allow it to easily be overridden by a
- subclass.
- """
- self.data.save(**kwargs)
-
- @property
- def data(self) -> Optional[Union[bytes, MSGFile]]:
- """
- Returns the attachment data.
- """
- return self.__data
-
- @property
- def randomFilename(self) -> str:
- """
- Returns the random filename to be used by this attachment.
- """
- try:
- return self.__randomName
- except AttributeError:
- self.regenerateRandomName()
- return self.__randomName
-
- @property
- def type(self) -> AttachmentType:
- """
- Returns the (internally used) type of the data.
- """
- return self.__type
-
-
-
-class BrokenAttachment(AttachmentBase):
- """
- An attachment that has suffered a fatal error. Will not generate from a
- NotImplementedError exception.
- """
-
- @property
- def type(self) -> AttachmentType:
- """
- Returns the (internally used) type of the data.
- """
- return AttachmentType.BROKEN
-
-class UnsupportedAttachment(AttachmentBase):
- """
- An attachment whose type is not currently supported.
- """
-
- def save(self, **kwargs) -> None:
- """
- Raises a NotImplementedError unless :param skipNotImplemented: is set to
- True. If it is, returns None to signify the attachment was skipped. This
- allows for the easy implementation of the option to skip this type of
- attachment.
- """
- if not kwargs.get('skipNotImplemented', False):
- raise NotImplementedError('Unsupported attachments cannot be saved.')
-
- @property
- def type(self) -> AttachmentType:
- """
- Returns the (internally used) type of the data.
- """
- return AttachmentType.UNSUPPORTED
diff --git a/extract_msg/attachment_base.py b/extract_msg/attachment_base.py
deleted file mode 100644
index 62a3938e..00000000
--- a/extract_msg/attachment_base.py
+++ /dev/null
@@ -1,418 +0,0 @@
-from __future__ import annotations
-
-
-__all__ = [
- 'AttachmentBase',
-]
-
-
-import datetime
-import logging
-
-from functools import partial
-from typing import Optional, Tuple, TYPE_CHECKING
-
-from .enums import AttachmentType, ErrorBehavior, PropertiesType
-from .exceptions import StandardViolationError
-from .named import NamedProperties
-from .prop import FixedLengthProp
-from .properties import Properties
-from .utils import tryGetMimetype, verifyPropertyId, verifyType
-
-
-# Allow for nice type checking.
-if TYPE_CHECKING:
- from .msg import MSGFile
-
-logger = logging.getLogger(__name__)
-logger.addHandler(logging.NullHandler())
-
-
-class AttachmentBase:
- """
- Stores the attachment data of a Message instance.
- Should the attachment be an embeded message, the
- class used to create it will be the same as the
- Message class used to create the attachment.
- """
-
- def __init__(self, msg, dir_):
- """
- :param msg: the Message instance that the attachment belongs to.
- :param dir_: the directory inside the msg file where the attachment is located.
- """
- self.__msg = msg
- self.__dir = dir_
- if not self.exists('__properties_version1.0'):
- if (msg.errorBehavior & ErrorBehavior.STANDARDS_VIOLATION):
- logger.error('Attachments MUST have a property stream.')
- else:
- raise StandardViolationError('Attachments MUST have a property stream.') from None
- self.__props = Properties(self._getStream('__properties_version1.0'), PropertiesType.ATTACHMENT)
- self.__namedProperties = NamedProperties(msg.named, self)
- self.__treePath = msg.treePath + (self,)
-
- def _ensureSet(self, variable, streamID, stringStream = True, **kwargs):
- """
- Ensures that the variable exists, otherwise will set it using the
- specified stream. After that, return said variable.
-
- If the specified stream is not a string stream, make sure to set
- :param stringStream: to False.
-
- :param overrideClass: Class/function to use to morph the data that was
- read. The data will be the first argument to the class's __init__
- function or the function itself, if that is what is provided. By
- default, this will be completely ignored if the value was not found.
- :param preserveNone: If true (default), causes the function to ignore
- :param overrideClass: when the value could not be found (is None).
- If this is changed to False, then the value will be used regardless.
- """
- try:
- return getattr(self, variable)
- except AttributeError:
- if stringStream:
- value = self._getStringStream(streamID)
- else:
- value = self._getStream(streamID)
- # Check if we should be overriding the data type for this instance.
- if kwargs:
- overrideClass = kwargs.get('overrideClass')
- if overrideClass is not None and (value is not None or not kwargs.get('preserveNone', True)):
- value = overrideClass(value)
- setattr(self, variable, value)
- return value
-
- def _ensureSetNamed(self, variable, propertyName : str, guid : str, **kwargs):
- """
- Ensures that the variable exists, otherwise will set it using the named
- property. After that, return said variable.
-
- :param overrideClass: Class/function to use to morph the data that was
- read. The data will be the first argument to the class's __init__
- function or the function itself, if that is what is provided. By
- default, this will be completely ignored if the value was not found.
- :param preserveNone: If true (default), causes the function to ignore
- :param overrideClass: when the value could not be found (is None).
- If this is changed to False, then the value will be used regardless.
- """
- try:
- return getattr(self, variable)
- except AttributeError:
- value = self.namedProperties.get((propertyName, guid))
- # Check if we should be overriding the data type for this instance.
- if kwargs:
- overrideClass = kwargs.get('overrideClass')
- if overrideClass is not None and (value is not None or not kwargs.get('preserveNone', True)):
- value = overrideClass(value)
- setattr(self, variable, value)
- return value
-
- def _ensureSetProperty(self, variable, propertyName, **kwargs):
- """
- Ensures that the variable exists, otherwise will set it using the
- property. After that, return said variable.
-
- :param overrideClass: Class/function to use to morph the data that was
- read. The data will be the first argument to the class's __init__
- function or the function itself, if that is what is provided. By
- default, this will be completely ignored if the value was not found.
- :param preserveNone: If true (default), causes the function to ignore
- :param overrideClass: when the value could not be found (is None).
- If this is changed to False, then the value will be used regardless.
- """
- try:
- return getattr(self, variable)
- except AttributeError:
- try:
- value = self.props[propertyName].value
- except (KeyError, AttributeError):
- value = None
- # Check if we should be overriding the data type for this instance.
- if kwargs:
- overrideClass = kwargs.get('overrideClass')
- if overrideClass is not None and (value is not None or not kwargs.get('preserveNone', True)):
- value = overrideClass(value)
- setattr(self, variable, value)
- return value
-
- def _ensureSetTyped(self, variable, _id, **kwargs):
- """
- Like the other ensure set functions, but designed for when something
- could be multiple types (where only one will be present). This way you
- have no need to set the type, it will be handled for you.
-
- :param overrideClass: Class/function to use to morph the data that was
- read. The data will be the first argument to the class's __init__
- function or the function itself, if that is what is provided. By
- default, this will be completely ignored if the value was not found.
- :param preserveNone: If true (default), causes the function to ignore
- :param overrideClass: when the value could not be found (is None).
- If this is changed to False, then the value will be used regardless.
- """
- try:
- return getattr(self, variable)
- except AttributeError:
- value = self._getTypedData(_id)
- # Check if we should be overriding the data type for this instance.
- if kwargs:
- overrideClass = kwargs.get('overrideClass')
- if overrideClass is not None and (value is not None or not kwargs.get('preserveNone', True)):
- value = overrideClass(value)
- setattr(self, variable, value)
- return value
-
- def _getStream(self, filename) -> Optional[bytes]:
- return self.__msg._getStream([self.__dir, filename])
-
- def _getStringStream(self, filename) -> Optional[str]:
- """
- Gets a string representation of the requested filename.
- Checks for both ASCII and Unicode representations and returns
- a value if possible. If there are both ASCII and Unicode
- versions, then :param prefer: specifies which will be
- returned.
- """
- return self.__msg._getStringStream([self.__dir, filename])
-
- def _getTypedData(self, id, _type = None):
- """
- Gets the data for the specified id as the type that it is
- supposed to be. :param id: MUST be a 4 digit hexadecimal
- string.
-
- If you know for sure what type the data is before hand,
- you can specify it as being one of the strings in the
- constant FIXED_LENGTH_PROPS_STRING or
- VARIABLE_LENGTH_PROPS_STRING.
- """
- verifyPropertyId(id)
- id = id.upper()
- found, result = self._getTypedStream('__substg1.0_' + id, _type)
- if found:
- return result
- else:
- found, result = self._getTypedProperty(id, _type)
- return result if found else None
-
- def _getTypedProperty(self, propertyID, _type = None):
- """
- Gets the property with the specified id as the type that it
- is supposed to be. :param id: MUST be a 4 digit hexadecimal
- string.
-
- If you know for sure what type the property is before hand,
- you can specify it as being one of the strings in the
- constant FIXED_LENGTH_PROPS_STRING or
- VARIABLE_LENGTH_PROPS_STRING.
- """
- verifyPropertyId(propertyID)
- verifyType(_type)
- propertyID = propertyID.upper()
- for x in (propertyID + _type,) if _type is not None else self.props:
- if x.startswith(propertyID):
- prop = self.props[x]
- return True, (prop.value if isinstance(prop, FixedLengthProp) else prop)
- return False, None
-
- def _getTypedStream(self, filename, _type = None):
- """
- Gets the contents of the specified stream as the type that
- it is supposed to be.
-
- Rather than the full filename, you should only feed this
- function the filename sans the type. So if the full name
- is "__substg1.0_001A001F", the filename this function
- should receive should be "__substg1.0_001A".
-
- If you know for sure what type the stream is before hand,
- you can specify it as being one of the strings in the
- constant FIXED_LENGTH_PROPS_STRING or
- VARIABLE_LENGTH_PROPS_STRING.
-
- If you have not specified the type, the type this function
- returns in many cases cannot be predicted. As such, when
- using this function it is best for you to check the type
- that it returns. If the function returns None, that means
- it could not find the stream specified.
- """
- return self.__msg._getTypedStream([self.__dir, filename], True, _type)
-
- def exists(self, filename) -> bool:
- """
- Checks if stream exists inside the attachment folder.
- """
- return self.__msg.exists([self.__dir, filename])
-
- def sExists(self, filename) -> bool:
- """
- Checks if the string stream exists inside the attachment folder.
- """
- return self.__msg.sExists([self.__dir, filename])
-
- def existsTypedProperty(self, id, _type = None) -> bool:
- """
- Determines if the stream with the provided id exists. The return of this
- function is 2 values, the first being a boolean for if anything was
- found, and the second being how many were found.
- """
- return self.__msg.existsTypedProperty(id, self.__dir, _type, True, self.__props)
-
- @property
- def attachmentEncoding(self) -> Optional[bytes]:
- """
- The encoding information about the attachment object. Will return
- b'*\x86H\x86\xf7\x14\x03\x0b\x01' if encoded in MacBinary format,
- otherwise it is unset.
- """
- return self._ensureSet('_attachmentEncoding', '__substg1.0_37020102', False)
-
- @property
- def additionalInformation(self) -> Optional[str]:
- """
- The additional information about the attachment. This property MUST be
- an empty string if attachmentEncoding is not set. Otherwise it MUST be
- set to a string of the format ":CREA:TYPE" where ":CREA" is the
- four-letter Macintosh file creator code and ":TYPE" is a four-letter
- Macintosh type code.
- """
- return self._ensureSet('_additionalInformation', '__substg1.0_370F')
-
- @property
- def cid(self) -> Optional[str]:
- """
- Returns the Content ID of the attachment, if it exists.
- """
- return self._ensureSet('_cid', '__substg1.0_3712')
-
- contendId = cid
-
- @property
- def dir(self):
- """
- Returns the directory inside the msg file where the attachment is
- located.
- """
- return self.__dir
-
- @property
- def displayName(self) -> Optional[str]:
- """
- Returns the display name of the folder.
- """
- return self._ensureSet('_displayName', '__substg1.0_3001')
-
- @property
- def exceptionReplaceTime(self) -> Optional[datetime.datetime]:
- """
- The original date and time at which the instance in the recurrence
- pattern would have occurred if it were not an exception.
-
- Only applicable if the attachment is an Exception object.
- """
- return self._ensureSetProperty('_exceptionReplaceTime', '7FF90040')
-
- @property
- def extension(self) -> Optional[str]:
- """
- The reported extension for the file.
- """
- return self._ensureSet('_extension', '__substg1.0_3703')
-
- @property
- def hidden(self) -> bool:
- """
- Indicates whether an Attachment object is hidden from the end user.
- """
- return self._ensureSetProperty('_hidden', '7FFE000B', overrideClass = bool, preserveNone = False)
-
- @property
- def isAttachmentContactPhoto(self) -> bool:
- """
- Whether the attachment is a contact photo for a Contact object.
- """
- return self._ensureSetProperty('_isAttachmentContactPhoto', '7FFF000B', overrideClass = bool, preserveNone = False)
-
- @property
- def longFilename(self) -> Optional[str]:
- """
- Returns the long file name of the attachment, if it exists.
- """
- return self._ensureSet('_longFilename', '__substg1.0_3707')
-
- @property
- def mimetype(self) -> Optional[str]:
- """
- The content-type mime header of the attachment, if specified.
- """
- return self._ensureSet('_mimetype', '__substg1.0_370E', overrideClass = partial(tryGetMimetype, self), preserveNone = False)
-
- @property
- def msg(self) -> MSGFile:
- """
- Returns the Message instance the attachment belongs to.
- """
- return self.__msg
-
- @property
- def name(self) -> Optional[str]:
- """
- The best name available for the file. Uses long filename before short.
- """
- if self.type is AttachmentType.MSG:
- if self.displayName:
- return self.displayName + '.msg'
- return self.longFilename or self.shortFilename
-
- @property
- def namedProperties(self) -> NamedProperties:
- """
- The NamedAttachmentProperties instance for this attachment.
- """
- return self.__namedProperties
-
- @property
- def payloadClass(self) -> Optional[str]:
- """
- The class name of an object that can display the contents of the
- message.
- """
- return self._ensureSet('_payloadClass', '__substg1.0_371A')
-
- @property
- def props(self) -> Properties:
- """
- Returns the Properties instance of the attachment.
- """
- return self.__props
-
- @property
- def renderingPosition(self) -> Optional[int]:
- """
- The offset, in redered characters, to use when rendering the attachment
- within the main message text. A value of 0xFFFFFFFF indicates a hidden
- attachment that is not to be rendered.
- """
- return self._ensureSetProperty('_renderingPosition', '370B0003')
-
- @property
- def shortFilename(self) -> Optional[str]:
- """
- Returns the short file name of the attachment, if it exists.
- """
- return self._ensureSet('_shortFilename', '__substg1.0_3704')
-
- @property
- def treePath(self) -> Tuple:
- """
- A path, as a tuple of instances, needed to get to this instance through
- the MSGFile-Attachment tree.
- """
- return self.__treePath
-
- @property
- def type(self) -> AttachmentType:
- """
- Returns the (internally used) type of the data.
- """
- return AttachmentType.UNKNOWN
diff --git a/extract_msg/attachments/__init__.py b/extract_msg/attachments/__init__.py
new file mode 100644
index 00000000..4edf59b6
--- /dev/null
+++ b/extract_msg/attachments/__init__.py
@@ -0,0 +1,144 @@
+from __future__ import annotations
+
+
+"""
+Submodule for attachment classes.
+"""
+
+__all__ = [
+ # Modules.
+ 'custom_att_handler',
+
+ # Classes.
+ 'Attachment',
+ 'AttachmentBase',
+ 'BrokenAttachment',
+ 'CustomAttachmentHandler',
+ 'EmbeddedMsgAttachment',
+ 'SignedAttachment',
+ 'UnsupportedAttachment',
+ 'WebAttachment',
+
+ # Functions.
+ 'initStandardAttachment',
+ 'registerHandler',
+]
+
+
+from . import custom_att_handler
+from .attachment import Attachment
+from .attachment_base import AttachmentBase
+from .broken_att import BrokenAttachment
+from .custom_att import CustomAttachment
+from .custom_att_handler import CustomAttachmentHandler, registerHandler
+from .emb_msg_att import EmbeddedMsgAttachment
+from .signed_att import SignedAttachment
+from .unsupported_att import UnsupportedAttachment
+from .web_att import WebAttachment
+
+import logging as _logging
+
+from typing import TYPE_CHECKING
+
+
+if TYPE_CHECKING:
+ from ..msg_classes import MSGFile
+
+_logger = _logging.getLogger(__name__)
+_logger.addHandler(_logging.NullHandler())
+
+
+def initStandardAttachment(msg : MSGFile, dir_) -> AttachmentBase:
+ """
+ Returns an instance of AttachmentBase for the attachment in the MSG file at
+ the specified internal directory.
+
+ :param errorBehavior: Used to tell the function what to do on errors.
+ """
+ from ..properties import PropertiesStore
+ from ..enums import ErrorBehavior, PropertiesType
+ from ..exceptions import (
+ FeatureNotImplemented, StandardViolationError,
+ UnrecognizedMSGTypeError
+ )
+
+ # First, create the properties store to check things like attachment type.
+ propertiesStream = msg._getStream([dir_, '__properties_version1.0'])
+ propStore = PropertiesStore(propertiesStream, PropertiesType.ATTACHMENT)
+
+ try:
+ # Now that we have the properties store, attempt to check what type it
+ # is.
+ if '37050003' not in propStore:
+ from ..properties.prop import createProp
+
+ _logger.warning(f'Attachment method property not found on attachment {dir_}. Code will attempt to guess the type.')
+ _logger.log(5, propStore)
+
+ # Because this condition is actually kind of a violation of the
+ # standard, we are just going to do this in a dumb way.
+ # Basically we are going to try to set the attach method
+ # *manually* just so I don't have to go and modify the
+ # following code.
+ if msg.exists([dir_, '__substg1.0_37010102']):
+ # Set it as data and call it a day.
+ propData = b'\x03\x00\x057\x07\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00'
+ elif msg.exists([dir_, '__substg1.0_3701000D']):
+ # If it is a folder and we have properties, call it an MSG
+ # file.
+ if msg.exists([dir_, '__substg1.0_3701000D/__properties_version1.0']):
+ propData = b'\x03\x00\x057\x07\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00'
+ else:
+ # Call if custom attachment data.
+ propData = b'\x03\x00\x057\x07\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00'
+ else:
+ # Can't autodetect it, so throw an error.
+ raise StandardViolationError(f'Attachment method missing on attachment {dir_}, and it could not be determined automatically.')
+
+ propStore._propDict['37050003'] = createProp(propData)
+
+ attMethod = propStore['37050003'].value & 7
+
+ if msg.exists([dir_, '__substg1.0_37010102']):
+ return Attachment(msg, dir_, propStore)
+ if msg.exists([dir_, '__substg1.0_3701000D']):
+ if attMethod != 5:
+ return CustomAttachment(msg, dir_, propStore)
+ else:
+ return EmbeddedMsgAttachment(msg, dir_, propStore)
+
+ if attMethod == 7:
+ return WebAttachment(msg, dir_, propStore)
+
+ # Error handling.
+ if attMethod == 1:
+ raise StandardViolationError('Attachments of type data MUST have a data stream.')
+ if attMethod == 0:
+ raise NotImplementedError('extract-msg does not support attachments of type afNone.')
+ if attMethod == 2:
+ raise NotImplementedError('extract-msg does not support attachments of type afByReference. Contact the developer for support.')
+ if attMethod == 4:
+ raise NotImplementedError('extract-msg does not support attachments of type afByReferenceOnly. Contact the developer for support.')
+
+ raise NotImplementedError(f'Could not determine attachment type ({attMethod})!')
+
+ except (FeatureNotImplemented,
+ NotImplementedError,
+ UnrecognizedMSGTypeError):
+ if ErrorBehavior.ATTACH_NOT_IMPLEMENTED in msg.errorBehavior:
+ _logger.exception(f'Error processing attachment at {dir_}')
+ return UnsupportedAttachment(msg, dir_, propStore)
+ else:
+ raise
+ except StandardViolationError:
+ if ErrorBehavior.STANDARDS_VIOLATION in msg.errorBehavior:
+ _logger.exception(f'Unresolvable standards violation in {dir_}')
+ return BrokenAttachment(msg, dir_, propStore)
+ else:
+ raise
+ except Exception:
+ if ErrorBehavior.ATTACH_BROKEN in msg.errorBehavior:
+ _logger.exception(f'Error processing attachment at {dir_}')
+ return BrokenAttachment(msg, dir_)
+ else:
+ raise
diff --git a/extract_msg/attachments/attachment.py b/extract_msg/attachments/attachment.py
new file mode 100644
index 00000000..2f056a26
--- /dev/null
+++ b/extract_msg/attachments/attachment.py
@@ -0,0 +1,198 @@
+from __future__ import annotations
+
+
+__all__ = [
+ 'Attachment',
+]
+
+
+import logging
+import os
+import pathlib
+import random
+import string
+import zipfile
+
+from typing import TYPE_CHECKING
+
+from .. import constants
+from .attachment_base import AttachmentBase
+from ..enums import AttachmentType, SaveType
+from ..utils import createZipOpen, inputToString, prepareFilename
+from ..properties import PropertiesStore
+
+
+# Allow for nice type checking.
+if TYPE_CHECKING:
+ from ..msg_classes.msg import MSGFile
+
+logger = logging.getLogger(__name__)
+logger.addHandler(logging.NullHandler())
+
+
+class Attachment(AttachmentBase):
+ """
+ A standard data attachment of an MSG file.
+ """
+
+ def __init__(self, msg : MSGFile, dir_, propStore : PropertiesStore):
+ """
+ :param msg: The MSGFile instance that the attachment belongs to.
+ :param dir_: The directory inside the MSG file where the attachment is
+ located.
+ :param propStore: The PropertiesStore instance for the attachment to
+ use.
+ """
+ super().__init__(msg, dir_, propStore)
+ self.__data = self._getStream('__substg1.0_37010102')
+
+ def getFilename(self, **kwargs) -> str:
+ """
+ Returns the filename to use for the attachment.
+
+ :param contentId: Use the contentId, if available.
+ :param customFilename: A custom name to use for the file.
+
+ If the filename starts with "UnknownFilename" then there is no guarentee
+ that the files will have exactly the same filename.
+ """
+ filename = None
+ customFilename = kwargs.get('customFilename')
+ if customFilename:
+ customFilename = str(customFilename)
+ # First we need to validate it. If there are invalid characters,
+ # this will detect it.
+ if constants.re.INVALID_FILENAME_CHARACTERS.search(customFilename):
+ raise ValueError('Invalid character found in customFilename. Must not contain any of the following characters: \\/:*?"<>|')
+ filename = customFilename
+ else:
+ # If not...
+ # Check if user wants to save the file under the Content-ID.
+ if kwargs.get('contentId', False):
+ filename = self.cid
+ # If we are here, try to get the filename however else we can.
+ if not filename:
+ filename = self.name
+ # Otherwise just make something up!
+ if not filename:
+ return self.randomFilename
+
+ return filename
+
+ def regenerateRandomName(self) -> str:
+ """
+ Used to regenerate the random filename used if the attachment cannot
+ find a usable filename.
+ """
+ self.__randomName = inputToString('UnknownFilename ' + \
+ ''.join(random.choice(string.ascii_uppercase + string.digits)
+ for _ in range(5)) + '.bin', 'ascii')
+
+ def save(self, **kwargs) -> constants.SAVE_TYPE:
+ """
+ Saves the attachment data.
+
+ The name of the file is determined by several factors. The first
+ thing that is checked is if you have provided :param customFilename:
+ to this function. If you have, that is the name that will be used.
+ If no custom name has been provided and :param contentId: is True,
+ the file will be saved using the content ID of the attachment. If
+ it is not found or :param contentId: is False, the long filename
+ will be used. If the long filename is not found, the short one will
+ be used. If after all of this a usable filename has not been found, a
+ random one will be used (accessible from `Attachment.randomFilename`).
+ After the name to use has been determined, it will then be shortened to
+ make sure that it is not more than the value of :param maxNameLength:.
+
+ To change the directory that the attachment is saved to, set the value
+ of :param customPath: when calling this function. The default save
+ directory is the working directory.
+
+ If you want to save the contents into a ZipFile or similar object,
+ either pass a path to where you want to create one or pass an instance
+ to :param zip:. If :param zip: is an instance, :param customPath: will
+ refer to a location inside the zip file.
+
+ :param extractEmbedded: If True, causes the attachment, should it be an
+ embedded MSG file, to save as a .msg file instead of calling it's
+ save function.
+ :param skipEmbedded: If True, skips saving this attachment if it is an
+ embedded MSG file.
+ """
+ # Get the filename to use.
+ filename = self.getFilename(**kwargs)
+
+ # Someone managed to have a null character here, so let's get rid of
+ # that
+ filename = prepareFilename(inputToString(filename, self.msg.stringEncoding))
+
+ # Get the maximum name length.
+ maxNameLength = kwargs.get('maxNameLength', 256)
+
+ # Make sure the filename is not longer than it should be.
+ if len(filename) > maxNameLength:
+ name, ext = os.path.splitext(filename)
+ filename = name[:maxNameLength - len(ext)] + ext
+
+ # Check if we are doing a zip file.
+ _zip = kwargs.get('zip')
+
+ createdZip = False
+ try:
+ # ZipFile handling.
+ if _zip:
+ # If we are doing a zip file, first check that we have been
+ # given a path.
+ if isinstance(_zip, (str, pathlib.Path)):
+ # If we have a path then we use the zip file.
+ _zip = zipfile.ZipFile(_zip, 'a', zipfile.ZIP_DEFLATED)
+ kwargs['zip'] = _zip
+ createdZip = True
+ # Path needs to be done in a special way if we are in a zip
+ # file.
+ customPath = pathlib.Path(kwargs.get('customPath', ''))
+ # Set the open command to be that of the zip file.
+ _open = createZipOpen(_zip.open)
+ # Zip files use w for writing in binary.
+ mode = 'w'
+ else:
+ customPath = pathlib.Path(kwargs.get('customPath', '.')).absolute()
+ mode = 'wb'
+ _open = open
+
+ fullFilename = self._handleFnc(_zip, filename, customPath, kwargs)
+
+ with _open(str(fullFilename), mode) as f:
+ f.write(self.__data)
+
+ return (SaveType.FILE, str(fullFilename))
+
+ finally:
+ # Close the ZipFile if this function created it.
+ if _zip and createdZip:
+ _zip.close()
+
+ @property
+ def data(self) -> bytes:
+ """
+ The bytes making up the attachment data.
+ """
+ return self.__data
+
+ @property
+ def randomFilename(self) -> str:
+ """
+ Returns the random filename to be used by this attachment.
+ """
+ try:
+ return self.__randomName
+ except AttributeError:
+ self.regenerateRandomName()
+ return self.__randomName
+
+ @property
+ def type(self) -> AttachmentType:
+ """
+ Returns an enum value that identifies the type of attachment.
+ """
+ return AttachmentType.DATA
diff --git a/extract_msg/attachments/attachment_base.py b/extract_msg/attachments/attachment_base.py
new file mode 100644
index 00000000..f6e2683f
--- /dev/null
+++ b/extract_msg/attachments/attachment_base.py
@@ -0,0 +1,584 @@
+from __future__ import annotations
+
+
+__all__ = [
+ 'AttachmentBase',
+]
+
+
+import abc
+import datetime
+import functools
+import logging
+import os
+import pathlib
+import weakref
+
+from functools import cached_property, partial
+from typing import List, Optional, Tuple, Type, TYPE_CHECKING
+
+from .. import constants
+from ..enums import AttachmentType
+from ..properties.named import NamedProperties
+from ..properties.prop import FixedLengthProp
+from ..properties.properties_store import PropertiesStore
+from ..utils import makeWeakRef, tryGetMimetype, verifyPropertyId, verifyType
+
+
+# Allow for nice type checking.
+if TYPE_CHECKING:
+ from ..msg_classes.msg import MSGFile
+
+logger = logging.getLogger(__name__)
+logger.addHandler(logging.NullHandler())
+
+
+class AttachmentBase(abc.ABC):
+ """
+ The base class for all Attachments used by the module, if not overriden.
+ """
+
+ def __init__(self, msg : MSGFile, dir_, propStore : PropertiesStore):
+ """
+ :param msg: the Message instance that the attachment belongs to.
+ :param dir_: the directory inside the msg file where the attachment is located.
+ :param propStore: The PropertiesStore instance for the attachment. If
+ not provided, it will be found automatically.
+ """
+ self.__msg = makeWeakRef(msg)
+ self.__dir = dir_
+ self.__props = propStore
+ self.__namedProperties = NamedProperties(msg.named, self)
+ self.__treePath = msg.treePath + [makeWeakRef(self)]
+
+ def _getNamedAs(self, propertyName : str, guid : str, overrideClass = None, preserveNone : bool = True):
+ """
+ Returns the named property, setting the class if specified.
+
+ :param overrideClass: Class/function to use to morph the data that was
+ read. The data will be the first argument to the class's __init__
+ function or the function itself, if that is what is provided. By
+ default, this will be completely ignored if the value was not found.
+ :param preserveNone: If true (default), causes the function to ignore
+ :param overrideClass: when the value could not be found (is None).
+ If this is changed to False, then the value will be used regardless.
+ """
+ value = self.namedProperties.get((propertyName, guid))
+ # Check if we should be overriding the data type for this instance.
+ if overrideClass is not None:
+ if value is not None or not preserveNone:
+ value = overrideClass(value)
+
+ return value
+
+ def _getPropertyAs(self, propertyName, overrideClass = None, preserveNone : bool = True):
+ """
+ Returns the property, setting the class if specified.
+
+ :param overrideClass: Class/function to use to morph the data that was
+ read. The data will be the first argument to the class's __init__
+ function or the function itself, if that is what is provided. By
+ default, this will be completely ignored if the value was not found.
+ :param preserveNone: If True (default), causes the function to ignore
+ :param overrideClass: when the value could not be found (is None).
+ If this is changed to False, then the value will be used regardless.
+ """
+ try:
+ value = self.props[propertyName].value
+ except (KeyError, AttributeError):
+ value = None
+ # Check if we should be overriding the data type for this instance.
+ if overrideClass is not None:
+ if (value is not None or not preserveNone):
+ value = overrideClass(value)
+
+ return value
+
+ def _getStream(self, filename) -> Optional[bytes]:
+ """
+ Gets a binary representation of the requested filename.
+
+ This should ALWAYS return a bytes object if it was found, otherwise
+ returns None.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
+ """
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Attachment instance has been garbage collected.')
+ return msg._getStream([self.__dir, filename])
+
+ def _getStreamAs(self, streamID, stringStream : bool = True, overrideClass = None, preserveNone : bool = True):
+ """
+ Returns the specified stream, modifying it to the class if specified.
+
+ If the specified stream is not a string stream, make sure to set
+ :param stringStream: to False.
+
+ :param overrideClass: Class/function to use to morph the data that was
+ read. The data will be the first argument to the class's __init__
+ function or the function itself, if that is what is provided. By
+ default, this will be completely ignored if the value was not found.
+ :param preserveNone: If true (default), causes the function to ignore
+ :param overrideClass: when the value could not be found (is None).
+ If this is changed to False, then the value will be used regardless.
+ """
+ if stringStream:
+ value = self._getStringStream(streamID)
+ else:
+ value = self._getStream(streamID)
+
+ # Check if we should be overriding the data type for this instance.
+ if overrideClass is not None:
+ if value is not None or not preserveNone:
+ value = overrideClass(value)
+
+ return value
+
+ def _getStringStream(self, filename) -> Optional[str]:
+ """
+ Gets a string representation of the requested filename.
+ Checks for both ASCII and Unicode representations and returns
+ a value if possible. If there are both ASCII and Unicode
+ versions, then :param prefer: specifies which will be
+ returned.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
+ """
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Attachment instance has been garbage collected.')
+ return msg._getStringStream([self.__dir, filename])
+
+ def _getTypedAs(self, _id : str, overrideClass = None, preserveNone : bool = True):
+ """
+ Like the other get as functions, but designed for when something
+ could be multiple types (where only one will be present). This way you
+ have no need to set the type, it will be handled for you.
+
+ :param overrideClass: Class/function to use to morph the data that was
+ read. The data will be the first argument to the class's __init__
+ function or the function itself, if that is what is provided. By
+ default, this will be completely ignored if the value was not found.
+ :param preserveNone: If true (default), causes the function to ignore
+ :param overrideClass: when the value could not be found (is None).
+ If this is changed to False, then the value will be used regardless.
+ """
+ value = self._getTypedData(_id)
+ # Check if we should be overriding the data type for this instance.
+ if overrideClass is not None:
+ if value is not None or not preserveNone:
+ value = overrideClass(value)
+
+ return value
+
+ def _getTypedData(self, id, _type = None):
+ """
+ Gets the data for the specified id as the type that it is
+ supposed to be. :param id: MUST be a 4 digit hexadecimal
+ string.
+
+ If you know for sure what type the data is before hand,
+ you can specify it as being one of the strings in the
+ constant FIXED_LENGTH_PROPS_STRING or
+ VARIABLE_LENGTH_PROPS_STRING.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
+ """
+ verifyPropertyId(id)
+ id = id.upper()
+ found, result = self._getTypedStream('__substg1.0_' + id, _type)
+ if found:
+ return result
+ else:
+ found, result = self._getTypedProperty(id, _type)
+ return result if found else None
+
+ def _getTypedProperty(self, propertyID, _type = None) -> Tuple[bool, Optional[object]]:
+ """
+ Gets the property with the specified id as the type that it
+ is supposed to be. :param id: MUST be a 4 digit hexadecimal
+ string.
+
+ If you know for sure what type the property is before hand,
+ you can specify it as being one of the strings in the
+ constant FIXED_LENGTH_PROPS_STRING or
+ VARIABLE_LENGTH_PROPS_STRING.
+ """
+ verifyPropertyId(propertyID)
+ verifyType(_type)
+ propertyID = propertyID.upper()
+ for x in (propertyID + _type,) if _type is not None else self.props:
+ if x.startswith(propertyID):
+ prop = self.props[x]
+ return True, (prop.value if isinstance(prop, FixedLengthProp) else prop)
+ return False, None
+
+ def _getTypedStream(self, filename, _type = None):
+ """
+ Gets the contents of the specified stream as the type that
+ it is supposed to be.
+
+ Rather than the full filename, you should only feed this
+ function the filename sans the type. So if the full name
+ is "__substg1.0_001A001F", the filename this function
+ should receive should be "__substg1.0_001A".
+
+ If you know for sure what type the stream is before hand,
+ you can specify it as being one of the strings in the
+ constant FIXED_LENGTH_PROPS_STRING or
+ VARIABLE_LENGTH_PROPS_STRING.
+
+ If you have not specified the type, the type this function
+ returns in many cases cannot be predicted. As such, when
+ using this function it is best for you to check the type
+ that it returns. If the function returns None, that means
+ it could not find the stream specified.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
+ """
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Attachment instance has been garbage collected.')
+ return msg._getTypedStream([self.__dir, filename], True, _type)
+
+ def _handleFnc(self, _zip, filename, customPath, kwargs) -> pathlib.Path:
+ """
+ "Handle Filename Conflict"
+
+ Internal function for use in determining how to modify the saving path
+ when a file with the same name already exists. This is mainly because
+ any save function that uses files will need to do this functionality.
+
+ :returns: A pathlib.Path object to where the file should be saved.
+ """
+ fullFilename = customPath / filename
+
+ overwriteExisting = kwargs.get('overwriteExisting', False)
+
+ if _zip:
+ # If we are writing to a zip file and are not overwriting.
+ if not overwriteExisting:
+ name, ext = os.path.splitext(filename)
+ nameList = _zip.namelist()
+ if str(fullFilename).replace('\\', '/') in nameList:
+ for i in range(2, 100):
+ testName = customPath / f'{name} ({i}){ext}'
+ if str(testName).replace('\\', '/') not in nameList:
+ return testName
+ else:
+ # If we couldn't find one that didn't exist.
+ raise FileExistsError(f'Could not create the specified file because it already exists ("{fullFilename}").')
+ else:
+ if not overwriteExisting and fullFilename.exists():
+ # Try to split the filename into a name and extention.
+ name, ext = os.path.splitext(filename)
+ # Try to add a number to it so that we can save without overwriting.
+ for i in range(2, 100):
+ testName = customPath / f'{name} ({i}){ext}'
+ if not testName.exists():
+ return testName
+ else:
+ # If we couldn't find one that didn't exist.
+ raise FileExistsError(f'Could not create the specified file because it already exists ("{fullFilename}").')
+
+ return fullFilename
+
+ def exists(self, filename) -> bool:
+ """
+ Checks if stream exists inside the attachment folder.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
+ """
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Attachment instance has been garbage collected.')
+ return msg.exists([self.__dir, filename])
+
+ def sExists(self, filename) -> bool:
+ """
+ Checks if the string stream exists inside the attachment folder.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
+ """
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Attachment instance has been garbage collected.')
+ return msg.sExists([self.__dir, filename])
+
+ def existsTypedProperty(self, id, _type = None) -> bool:
+ """
+ Determines if the stream with the provided id exists. The return of this
+ function is 2 values, the first being a boolean for if anything was
+ found, and the second being how many were found.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
+ """
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Attachment instance has been garbage collected.')
+ return msg.existsTypedProperty(id, self.__dir, _type, True, self.__props)
+
+ @abc.abstractmethod
+ def getFilename(self, **kwargs) -> str:
+ """
+ Returns the filename to use for the attachment.
+
+ :param contentId: Use the contentId, if available.
+ :param customFilename: A custom name to use for the file.
+
+ If the filename starts with "UnknownFilename" then there is no guarentee
+ that the files will have exactly the same filename.
+ """
+
+ @abc.abstractmethod
+ def save(self, **kwargs) -> constants.SAVE_TYPE:
+ """
+ Saves the attachment data.
+
+ The name of the file is determined by the logic of the getFilename
+ function. If you are a developer, ensure that you use this behavior.
+
+ To change the directory that the attachment is saved to, set the value
+ of :param customPath: when calling this function. The default save
+ directory is the working directory.
+
+ If you want to save the contents into a ZipFile or similar object,
+ either pass a path to where you want to create one or pass an instance
+ to :param zip:. If :param zip: is an instance, :param customPath: will
+ refer to a location inside the zip file.
+
+ :param extractEmbedded: If True, causes the attachment, should it be an
+ embedded MSG file, to save as a .msg file instead of calling it's
+ save function.
+ :param skipEmbedded: If True, skips saving this attachment if it is an
+ embedded MSG file.
+
+ :returns: A tuple that specifies how the data was saved. The value of
+ the first item specifies what the second value will be.
+ """
+
+ @functools.cached_property
+ def attachmentEncoding(self) -> Optional[bytes]:
+ """
+ The encoding information about the attachment object. Will return
+ b'*\\x86H\\x86\\xf7\\x14\\x03\\x0b\\x01' if encoded in MacBinary format,
+ otherwise it is unset.
+ """
+ return self._getStream('__substg1.0_37020102')
+
+ @functools.cached_property
+ def additionalInformation(self) -> Optional[str]:
+ """
+ The additional information about the attachment. This property MUST be
+ an empty string if attachmentEncoding is not set. Otherwise it MUST be
+ set to a string of the format ":CREA:TYPE" where ":CREA" is the
+ four-letter Macintosh file creator code and ":TYPE" is a four-letter
+ Macintosh type code.
+ """
+ return self._getStringStream('__substg1.0_370F')
+
+ @functools.cached_property
+ def cid(self) -> Optional[str]:
+ """
+ Returns the Content ID of the attachment, if it exists.
+ """
+ return self._getStringStream('__substg1.0_3712')
+
+ @cached_property
+ def clsid(self) -> str:
+ """
+ Returns the CLSID for the data stream/storage of the attachment.
+ """
+ # Set some default values.
+ clsid = '00000000-0000-0000-0000-000000000000'
+ dataStream = None
+
+ # See if we can find the data stream/storage.
+ if self.type in (AttachmentType.CUSTOM, AttachmentType.MSG):
+ dataStream = [self.__dir, '__substg1.0_3701000D']
+ elif self.type is AttachmentType.DATA:
+ dataStream = [self.__dir, '__substg1.0_37010102']
+ elif self.type is AttachmentType.UNSUPPORTED:
+ # Special check for custom attachments.
+ if self.exists('__substg1.0_3701000D'):
+ dataStream = [self.__dir, '__substg1.0_3701000D']
+ elif self.exists('__substg1.0_37010102'):
+ dataStream = [self.__dir, '__substg1.0_37010102']
+
+ # If we found the right item, get the CLSID.
+ if dataStream:
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Attachment instance has been garbage collected.')
+ clsid = msg._getOleEntry(dataStream).clsid or clsid
+
+ return clsid
+
+ @property
+ def contentId(self) -> Optional[str]:
+ return self.cid
+
+ @property
+ @abc.abstractmethod
+ def data(self) -> Optional[object]:
+ """
+ The attachment data, if any. Returns None if there is no data to save.
+ """
+
+ @functools.cached_property
+ def dataType(self) -> Optional[Type[type]]:
+ """
+ The class that the data type will use, if it can be retrieved.
+
+ This is a safe way to do type checking on data before knowing if it will
+ raise an exception. Returns None if no data will be returns or if an
+ exception will be raised.
+ """
+ try:
+ return None if self.data is None else self.data.__class__
+ except Exception:
+ # All exceptions that accessing data would cause should be silenced.
+ return None
+
+ @property
+ def dir(self) -> str:
+ """
+ Returns the directory inside the MSG file where the attachment is
+ located.
+ """
+ return self.__dir
+
+ @functools.cached_property
+ def displayName(self) -> Optional[str]:
+ """
+ Returns the display name of the folder.
+ """
+ return self._getStringStream('__substg1.0_3001')
+
+ @functools.cached_property
+ def exceptionReplaceTime(self) -> Optional[datetime.datetime]:
+ """
+ The original date and time at which the instance in the recurrence
+ pattern would have occurred if it were not an exception.
+
+ Only applicable if the attachment is an Exception object.
+ """
+ return self._getPropertyAs('7FF90040')
+
+ @functools.cached_property
+ def extension(self) -> Optional[str]:
+ """
+ The reported extension for the file.
+ """
+ return self._getStringStream('__substg1.0_3703')
+
+ @functools.cached_property
+ def hidden(self) -> bool:
+ """
+ Indicates whether an Attachment object is hidden from the end user.
+ """
+ return self._getPropertyAs('7FFE000B', bool, False)
+
+ @functools.cached_property
+ def isAttachmentContactPhoto(self) -> bool:
+ """
+ Whether the attachment is a contact photo for a Contact object.
+ """
+ return self._getPropertyAs('7FFF000B', bool, False)
+
+ @functools.cached_property
+ def longFilename(self) -> Optional[str]:
+ """
+ Returns the long file name of the attachment, if it exists.
+ """
+ return self._getStringStream('__substg1.0_3707')
+
+ @functools.cached_property
+ def longPathname(self) -> Optional[str]:
+ """
+ The fully qualified path and file name with extension.
+ """
+ return self._getStringStream('__substg1.0_370D')
+
+ @functools.cached_property
+ def mimetype(self) -> Optional[str]:
+ """
+ The content-type mime header of the attachment, if specified.
+ """
+ return self._getStreamAs('__substg1.0_370E', partial(tryGetMimetype, self), False)
+
+ @property
+ def msg(self) -> MSGFile:
+ """
+ Returns the Message instance the attachment belongs to.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
+ """
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Attachment instance has been garbage collected.')
+ return msg
+
+ @functools.cached_property
+ def name(self) -> Optional[str]:
+ """
+ The best name available for the file. Uses long filename before short.
+ """
+ if self.type is AttachmentType.MSG:
+ if self.displayName:
+ return self.displayName + '.msg'
+ return self.longFilename or self.shortFilename
+
+ @property
+ def namedProperties(self) -> NamedProperties:
+ """
+ The NamedAttachmentProperties instance for this attachment.
+ """
+ return self.__namedProperties
+
+ @functools.cached_property
+ def payloadClass(self) -> Optional[str]:
+ """
+ The class name of an object that can display the contents of the
+ message.
+ """
+ return self._getStringStream('__substg1.0_371A')
+
+ @property
+ def props(self) -> PropertiesStore:
+ """
+ Returns the Properties instance of the attachment.
+ """
+ return self.__props
+
+ @functools.cached_property
+ def renderingPosition(self) -> Optional[int]:
+ """
+ The offset, in redered characters, to use when rendering the attachment
+ within the main message text. A value of 0xFFFFFFFF indicates a hidden
+ attachment that is not to be rendered.
+ """
+ return self._getPropertyAs('370B0003')
+
+ @property
+ def shortFilename(self) -> Optional[str]:
+ """
+ Returns the short file name of the attachment, if it exists.
+ """
+ return self._getStringStream('__substg1.0_3704')
+
+ @property
+ def treePath(self) -> List[weakref.ReferenceType]:
+ """
+ A path, as a tuple of instances, needed to get to this instance through
+ the MSGFile-Attachment tree.
+ """
+ return self.__treePath
+
+ @property
+ @abc.abstractmethod
+ def type(self) -> AttachmentType:
+ """
+ Returns an enum value that identifies the type of attachment.
+ """
diff --git a/extract_msg/attachments/broken_att.py b/extract_msg/attachments/broken_att.py
new file mode 100644
index 00000000..d272bb3e
--- /dev/null
+++ b/extract_msg/attachments/broken_att.py
@@ -0,0 +1,42 @@
+__all__ = [
+ 'BrokenAttachment',
+]
+
+
+from .. import constants
+from .attachment_base import AttachmentBase
+from ..enums import AttachmentType, SaveType
+
+
+class BrokenAttachment(AttachmentBase):
+ """
+ An attachment that has suffered a fatal error. Will not generate from a
+ NotImplementedError exception.
+ """
+
+ def getFilename(self, **_) -> str:
+ raise NotImplementedError('Broken attachments cannot be saved.')
+
+ def save(self, **kwargs) -> constants.SAVE_TYPE:
+ """
+ Raises a NotImplementedError unless :param skipNotImplemented: is set to
+ True. If it is, returns a value that indicates no data was saved.
+ """
+ if kwargs.get('skipNotImplemented', False):
+ return (SaveType.NONE, None)
+
+ raise NotImplementedError('Broken attachments cannot be saved.')
+
+ @property
+ def data(self) -> None:
+ """
+ Broken attachments have no data.
+ """
+ return None
+
+ @property
+ def type(self) -> AttachmentType:
+ """
+ Returns an enum value that identifies the type of attachment.
+ """
+ return AttachmentType.BROKEN
\ No newline at end of file
diff --git a/extract_msg/attachments/custom_att.py b/extract_msg/attachments/custom_att.py
new file mode 100644
index 00000000..93f424e1
--- /dev/null
+++ b/extract_msg/attachments/custom_att.py
@@ -0,0 +1,175 @@
+from __future__ import annotations
+
+
+__all__ = [
+ 'CustomAttachment',
+]
+
+
+import os
+import pathlib
+import random
+import string
+import zipfile
+
+from typing import Optional, TYPE_CHECKING
+
+from .. import constants
+from .attachment_base import AttachmentBase
+from .custom_att_handler import CustomAttachmentHandler, getHandler
+from ..enums import AttachmentType, SaveType
+from ..utils import createZipOpen, inputToString, prepareFilename
+
+
+if TYPE_CHECKING:
+ from ..msg_classes import MSGFile
+ from ..properties import PropertiesStore
+
+
+_saveDoc = AttachmentBase.save.__doc__
+
+
+class CustomAttachment(AttachmentBase):
+ """
+ The attachment entry for custom attachments.
+ """
+
+ def __init__(self, msg : MSGFile, dir_, propStore : PropertiesStore):
+ super().__init__(msg, dir_, propStore)
+
+ self.__customHandler = getHandler(self)
+ self.__data = self.__customHandler.data
+
+
+ def getFilename(self, **kwargs) -> str:
+ """
+ Returns the filename to use for the attachment.
+
+ :param contentId: Use the contentId, if available.
+ :param customFilename: A custom name to use for the file.
+
+ If the filename starts with "UnknownFilename" then there is no guarentee
+ that the files will have exactly the same filename.
+ """
+ filename = None
+ customFilename = kwargs.get('customFilename')
+ if customFilename:
+ customFilename = str(customFilename)
+ # First we need to validate it. If there are invalid characters,
+ # this will detect it.
+ if constants.re.INVALID_FILENAME_CHARACTERS.search(customFilename):
+ raise ValueError('Invalid character found in customFilename. Must not contain any of the following characters: \\/:*?"<>|')
+ filename = customFilename
+ else:
+ # If not...
+ # Check if user wants to save the file under the Content-ID.
+ if kwargs.get('contentId', False):
+ filename = self.cid
+ # Try to get the name from the custom handler.
+ filename = self.customHandler.name
+ # If we are here, try to get the filename however else we can.
+ if not filename:
+ filename = self.name
+ # Otherwise just make something up!
+ if not filename:
+ return self.randomFilename
+
+ return filename
+
+ def regenerateRandomName(self) -> str:
+ """
+ Used to regenerate the random filename used if the attachment cannot
+ find a usable filename.
+ """
+ self.__randomName = 'UnknownFilename ' + \
+ ''.join(random.choice(string.ascii_uppercase + string.digits)
+ for _ in range(5)) + '.bin'
+
+ def save(self, **kwargs) -> constants.SAVE_TYPE:
+ # Immediate check to see if there is anything to save.
+ if self.data is None:
+ return (SaveType.NONE, None)
+
+ # Get the filename to use.
+ filename = self.getFilename(**kwargs)
+
+ # Someone managed to have a null character here, so let's get rid of
+ # that
+ filename = prepareFilename(inputToString(filename, self.msg.stringEncoding))
+
+ # Get the maximum name length.
+ maxNameLength = kwargs.get('maxNameLength', 256)
+
+ # Make sure the filename is not longer than it should be.
+ if len(filename) > maxNameLength:
+ name, ext = os.path.splitext(filename)
+ filename = name[:maxNameLength - len(ext)] + ext
+
+ # Check if we are doing a zip file.
+ _zip = kwargs.get('zip')
+
+ createdZip = False
+ try:
+ # ZipFile handling.
+ if _zip:
+ # If we are doing a zip file, first check that we have been given a path.
+ if isinstance(_zip, (str, pathlib.Path)):
+ # If we have a path then we use the zip file.
+ _zip = zipfile.ZipFile(_zip, 'a', zipfile.ZIP_DEFLATED)
+ kwargs['zip'] = _zip
+ createdZip = True
+ # Path needs to be done in a special way if we are in a zip file.
+ customPath = pathlib.Path(kwargs.get('customPath', ''))
+ # Set the open command to be that of the zip file.
+ _open = createZipOpen(_zip.open)
+ # Zip files use w for writing in binary.
+ mode = 'w'
+ else:
+ customPath = pathlib.Path(kwargs.get('customPath', '.')).absolute()
+ mode = 'wb'
+ _open = open
+
+ fullFilename = self._handleFnc(_zip, filename, customPath, kwargs)
+
+ with _open(str(fullFilename), mode) as f:
+ f.write(self.__data)
+
+ return (SaveType.FILE, str(fullFilename))
+ finally:
+ # Close the ZipFile if this function created it.
+ if _zip and createdZip:
+ _zip.close()
+
+
+ @property
+ def customHandler(self) -> Optional[CustomAttachmentHandler]:
+ """
+ The instance of the custom handler associated with this attachment, if
+ it has one.
+ """
+ return self.__customHandler
+
+ @property
+ def data(self) -> Optional[bytes]:
+ """
+ The attachment data, if any. Returns None if there is no data to save.
+ """
+ return self.__data
+
+ @property
+ def randomFilename(self) -> str:
+ """
+ Returns the random filename to be used by this attachment.
+ """
+ try:
+ return self.__randomName
+ except AttributeError:
+ self.regenerateRandomName()
+ return self.__randomName
+
+ @property
+ def type(self) -> AttachmentType:
+ """
+ Returns an enum value that identifies the type of attachment.
+ """
+ return AttachmentType.CUSTOM
diff --git a/extract_msg/attachments/custom_att_handler/__init__.py b/extract_msg/attachments/custom_att_handler/__init__.py
new file mode 100644
index 00000000..b3318f4a
--- /dev/null
+++ b/extract_msg/attachments/custom_att_handler/__init__.py
@@ -0,0 +1,79 @@
+from __future__ import annotations
+
+
+"""
+Submodule designed to help with saving and using custom attachments. Custom
+attachments are those follow standards not defined in the MSG documentation. Use
+the function `getHandler` to get an instance of a subclass of
+CustomAttachmentHandler.
+
+CustomAttachmentHandler subclasses will all define the following methods:
+ injectHtml: A method which takes HTML and inserts the
+
+It should hopefully be completely unnecessary for your code to know what type of
+handler it is using, as the abstract base class should give all of the functions
+you would typically want.
+
+If you would like to add your own handler, simply subclass
+CustomAttachmentHandler and add it using the `registerHandler` function.
+"""
+
+__all__ = [
+ # Classes.
+ 'CustomAttachmentHandler',
+ 'OutlookImageDIB',
+
+ # Functions.
+ 'getHandler',
+ 'registerHandler',
+]
+
+
+from typing import List, Type, TYPE_CHECKING
+
+from .custom_handler import CustomAttachmentHandler
+
+
+# Create a way to register handlers.
+_knownHandlers : List[CustomAttachmentHandler] = []
+
+def registerHandler(handler : Type[CustomAttachmentHandler]) -> None:
+ """
+ Registers the CustomAttachmentHandler subclass as a handler.
+
+ :raises TypeError: The handler was not a subclass of
+ CustomAttachmentHandler.
+ """
+ # Make sure it is a subclass of CustomAttachmentHandler.
+ if not isinstance(handler, type):
+ raise ValueError(':param handler: must be a class, not an instance of a class.')
+ if not issubclass(handler, CustomAttachmentHandler):
+ raise ValueError(':param handler: must be a subclass of CustomAttachmentHandler.')
+ _knownHandlers.append(handler)
+
+
+
+# Import built-in handler modules. They will all automatically register their
+# respecive handler(s).
+from .outlook_image_dib import OutlookImageDIB
+
+
+if TYPE_CHECKING:
+ from ..attachment_base import AttachmentBase
+
+
+# Function designed to route to the correct handler.
+def getHandler(attachment : AttachmentBase) -> CustomAttachmentHandler:
+ """
+ Takes an attachment and uses it to find the correct handler. Returns an
+ instance created using the specified attachment.
+
+ :raises NotImplementedError: No handler could be found.
+ :raises ValueError: A handler was found, but something was wrong with the
+ attachment data.
+ """
+ for handler in _knownHandlers:
+ if handler.isCorrectHandler(attachment):
+ return handler(attachment)
+
+ raise NotImplementedError('No valid handler could be found for the attachment. Contact the developers for help.')
diff --git a/extract_msg/attachments/custom_att_handler/custom_handler.py b/extract_msg/attachments/custom_att_handler/custom_handler.py
new file mode 100644
index 00000000..69419f85
--- /dev/null
+++ b/extract_msg/attachments/custom_att_handler/custom_handler.py
@@ -0,0 +1,75 @@
+from __future__ import annotations
+
+
+__all__ = [
+ 'CustomAttachmentHandler',
+]
+
+
+import abc
+
+from typing import Optional, TYPE_CHECKING
+
+
+if TYPE_CHECKING:
+ from ..attachment_base import AttachmentBase
+
+
+class CustomAttachmentHandler(abc.ABC):
+ """
+ A class designed to help with custom attachments that may require parsing in
+ special ways that are completely different from one another.
+ """
+
+ def __init__(self, attachment : AttachmentBase):
+ super().__init__()
+ self.__att = attachment
+
+ @classmethod
+ @abc.abstractmethod
+ def isCorrectHandler(cls, attachment : AttachmentBase) -> bool:
+ """
+ Checks if this is the correct handler for the attachment.
+ """
+
+ @abc.abstractmethod
+ def generateRtf(self) -> Optional[bytes]:
+ """
+ Generates the RTF to inject in place of the \objattph tag.
+
+ If this function should do nothing, returns None.
+ """
+
+ @property
+ def attachment(self):
+ """
+ The attachment this handler is associated with.
+ """
+ return self.__att
+
+ @property
+ @abc.abstractmethod
+ def data(self) -> bytes:
+ """
+ Gets the data for the attachment.
+
+ If an attachment should do nothing when saving, returns None.
+ """
+
+ @property
+ @abc.abstractmethod
+ def name(self) -> Optional[str]:
+ """
+ Returns the name to be used when saving the attachment.
+ """
+
+ @property
+ @abc.abstractmethod
+ def obj(self) -> Optional[object]:
+ """
+ Returns an object representing the data. May return the same as
+ :property data:.
+
+ If there is no object to represent the custom attachment, including
+ bytes, returns None.
+ """
\ No newline at end of file
diff --git a/extract_msg/attachments/custom_att_handler/outlook_image_dib.py b/extract_msg/attachments/custom_att_handler/outlook_image_dib.py
new file mode 100644
index 00000000..b51c87c8
--- /dev/null
+++ b/extract_msg/attachments/custom_att_handler/outlook_image_dib.py
@@ -0,0 +1,142 @@
+from __future__ import annotations
+
+
+__all__ = [
+ 'OutlookImage',
+]
+
+
+import struct
+
+from typing import Optional, TYPE_CHECKING
+
+from . import registerHandler
+from .custom_handler import CustomAttachmentHandler
+from ...enums import DVAspect, InsecureFeatures
+from ...exceptions import SecurityError
+
+
+if TYPE_CHECKING:
+ from ..attachment_base import AttachmentBase
+
+_ST_OLE = struct.Struct(' bool:
+ if attachment.clsid != '00000316-0000-0000-C000-000000000046':
+ return False
+
+ # Check for the required streams.
+ if not attachment.exists('__substg1.0_3701000D/CONTENTS'):
+ return False
+ if not attachment.exists('__substg1.0_3701000D/\x01Ole'):
+ return False
+ if not attachment.exists('__substg1.0_3701000D/\x03MailStream'):
+ return False
+
+ return True
+
+ def generateRtf(self) -> Optional[bytes]:
+ """
+ Generates the RTF to inject in place of the \objattph tag.
+
+ If this function should do nothing, returns None.
+
+ This function requires PIL or Pillow. If neither are found, raises an
+ import error.
+ """
+ if InsecureFeatures.PIL_IMAGE_PARSING not in self.attachment.msg.insecureFeatures:
+ raise SecurityError('Generating the RTF for a custom attachment requires the insecure feature PIL_IMAGE_PARSING.')
+
+ try:
+ import PIL.Image
+ except ImportError:
+ raise ImportError('PIL or Pillow is required for inserting an Outlook Image into the body.')
+
+ # First, convert the bitmap into a PNG so we can insert it into the
+ # body.
+ import io
+
+ # Note, use self.data instead of self.__data to allow support for
+ # extensions.
+ with PIL.Image.open(io.BytesIO(self.data)) as img:
+ out = io.BytesIO()
+ img.save(out, 'PNG')
+
+ hexData = out.getvalue().hex()
+
+ inject = '{\\*\\shppict\n{\\pict\\picscalex100\\picscaley100'
+ inject += f'\\picw{img.width}\\pich{img.height}'
+ inject += f'\\picwgoal{self.__xtwips}\\pichgoal{self.__ytwips}\n'
+ inject += '\\pngblip ' + hexData + '}}'
+
+ return inject.encode()
+
+ @property
+ def data(self) -> bytes:
+ return self.__data
+
+ @property
+ def name(self) -> str:
+ # Try to get the name from the attachment. If that fails, name it based
+ # on the number.
+ if not (name := self.attachment.name):
+ name = f'attachment {int(self.attachment.dir[-8:], 16)}'
+ return name + '.bmp'
+
+ @property
+ def obj(self) -> bytes:
+ return self.data
+
+
+
+registerHandler(OutlookImageDIB)
diff --git a/extract_msg/attachments/emb_msg_att.py b/extract_msg/attachments/emb_msg_att.py
new file mode 100644
index 00000000..8ead803a
--- /dev/null
+++ b/extract_msg/attachments/emb_msg_att.py
@@ -0,0 +1,137 @@
+from __future__ import annotations
+
+
+__all__ = [
+ 'EmbeddedMsgAttachment',
+]
+
+
+import os
+import pathlib
+import zipfile
+
+from typing import TYPE_CHECKING
+
+from .. import constants
+from .attachment_base import AttachmentBase
+from ..enums import AttachmentType, SaveType
+from ..open_msg import openMsg
+from ..utils import createZipOpen, prepareFilename
+
+
+if TYPE_CHECKING:
+ from ..msg_classes import MSGFile
+ from ..properties import PropertiesStore
+
+
+_saveDoc = AttachmentBase.save.__doc__
+
+
+class EmbeddedMsgAttachment(AttachmentBase):
+ """
+ The attachment entry for an Embedded MSG file.
+ """
+
+ def __init__(self, msg : MSGFile, dir_, propertiesStore : PropertiesStore):
+ super().__init__(msg, dir_, propertiesStore)
+ self.__prefix = msg.prefixList + [dir_, '__substg1.0_3701000D']
+ self.__data = openMsg(self.msg.path, prefix = self.__prefix, parentMsg = self.msg, treePath = self.treePath, **self.msg.kwargs)
+
+ def getFilename(self, **kwargs) -> str:
+ """
+ Returns the filename to use for the attachment.
+
+ :param contentId: Use the contentId, if available.
+ :param customFilename: A custom name to use for the file.
+
+ If the filename starts with "UnknownFilename" then there is no guarentee
+ that the files will have exactly the same filename.
+ """
+ customFilename = kwargs.get('customFilename')
+ if customFilename:
+ customFilename = str(customFilename)
+ # First we need to validate it. If there are invalid characters,
+ # this will detect it.
+ if constants.re.INVALID_FILENAME_CHARACTERS.search(customFilename):
+ raise ValueError('Invalid character found in customFilename. Must not contain any of the following characters: \\/:*?"<>|')
+ return customFilename
+ else:
+ return self.name
+
+ def save(self, **kwargs) -> constants.SAVE_TYPE:
+ # First check if we are skipping embedded messages and stop
+ # *immediately* if we are.
+ if kwargs.get('skipEmbedded'):
+ return (SaveType.NONE, None)
+
+ # We only need to handle things if we are saving as bytes.
+ if kwargs.get('extractEmbedded', False):
+ # Get the filename to use.
+ filename = self.getFilename(**kwargs)
+
+ # Someone managed to have a null character here, so let's get rid of
+ # that
+ filename = prepareFilename(filename)
+
+ # Get the maximum name length.
+ maxNameLength = kwargs.get('maxNameLength', 256)
+
+ # Make sure the filename is not longer than it should be.
+ if len(filename) > maxNameLength:
+ name, ext = os.path.splitext(filename)
+ filename = name[:maxNameLength - len(ext)] + ext
+
+ # Check if we are doing a zip file.
+ _zip = kwargs.get('zip')
+
+ createdZip = False
+ try:
+ # ZipFile handling.
+ if _zip:
+ # If we are doing a zip file, first check that we have been given a path.
+ if isinstance(_zip, (str, pathlib.Path)):
+ # If we have a path then we use the zip file.
+ _zip = zipfile.ZipFile(_zip, 'a', zipfile.ZIP_DEFLATED)
+ kwargs['zip'] = _zip
+ createdZip = True
+ # Path needs to be done in a special way if we are in a zip file.
+ customPath = pathlib.Path(kwargs.get('customPath', ''))
+ # Set the open command to be that of the zip file.
+ _open = createZipOpen(_zip.open)
+ # Zip files use w for writing in binary.
+ mode = 'w'
+ else:
+ customPath = pathlib.Path(kwargs.get('customPath', '.')).absolute()
+ mode = 'wb'
+ _open = open
+
+ fullFilename = self._handleFnc(_zip, filename, customPath, kwargs)
+
+ with _open(str(fullFilename), mode) as f:
+ self.data.export(f)
+
+ return (SaveType.FILE, str(fullFilename))
+ finally:
+ # Close the ZipFile if this function created it.
+ if _zip and createdZip:
+ _zip.close()
+ else:
+ # If we are letting the MSG file create stuff, just let it handle
+ # everything.
+ return self.data.save(**kwargs)
+
+ save.__doc__ = _saveDoc
+
+ @property
+ def data(self) -> MSGFile:
+ """
+ Returns the attachment data.
+ """
+ return self.__data
+
+ @property
+ def type(self) -> AttachmentType:
+ """
+ Returns an enum value that identifies the type of attachment.
+ """
+ return AttachmentType.MSG
\ No newline at end of file
diff --git a/extract_msg/signed_attachment.py b/extract_msg/attachments/signed_att.py
similarity index 63%
rename from extract_msg/signed_attachment.py
rename to extract_msg/attachments/signed_att.py
index 17d1bc97..aafcf104 100644
--- a/extract_msg/signed_attachment.py
+++ b/extract_msg/attachments/signed_att.py
@@ -10,17 +10,20 @@
import logging
import os
import pathlib
+import weakref
import zipfile
-from typing import Tuple, TYPE_CHECKING, Union
+from typing import List, Optional, Type, TYPE_CHECKING, Union
-from .enums import AttachmentType
-from .utils import createZipOpen, inputToString, openMsg, prepareFilename
+from .. import constants
+from ..enums import AttachmentType, SaveType
+from ..open_msg import openMsg
+from ..utils import createZipOpen, inputToString, makeWeakRef, prepareFilename
# Allow for nice type checking.
if TYPE_CHECKING:
- from .msg import MSGFile
+ from ..msg_classes.msg import MSGFile
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
@@ -38,9 +41,9 @@ def __init__(self, msg, data : bytes, name : str, mimetype : str, node : email.m
self.__asBytes = data
self.__name = name
self.__mimetype = mimetype
- self.__msg = msg
+ self.__msg = makeWeakRef(msg)
self.__node = node
- self.__treePath = msg.treePath + (self,)
+ self.__treePath = msg.treePath + [makeWeakRef(self)]
self.__data = None
# To add support for embedded MSG files, we are going to completely
@@ -59,7 +62,7 @@ def __init__(self, msg, data : bytes, name : str, mimetype : str, node : email.m
if self.__data is None:
self.__data = data
- def save(self, **kwargs):
+ def save(self, **kwargs) -> constants.SAVE_TYPE:
"""
Saves the attachment data.
@@ -83,11 +86,20 @@ def save(self, **kwargs):
either pass a path to where you want to create one or pass an instance
to :param zip:. If :param zip: is an instance, :param customPath: will
refer to a location inside the zip file.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
"""
# First check if we are skipping embedded messages and stop
# *immediately* if we are.
if self.type is AttachmentType.SIGNED_EMBEDDED and kwargs.get('skipEmbedded'):
- return None
+ return (SaveType.NONE, None)
+
+ # If we are running the save function for the MSG file, just let it
+ # handle everything.
+ if (self.type is AttachmentType.SIGNED_EMBEDDED and
+ not kwargs.get('extractEmbedded', False)):
+ return self.saveEmbededMessage(**kwargs)
# Check if the user has specified a custom filename
filename = self.name
@@ -106,84 +118,54 @@ def save(self, **kwargs):
# Check if we are doing a zip file.
_zip = kwargs.get('zip')
- # ZipFile handling.
- if _zip:
- # If we are doing a zip file, first check that we have been given a path.
- if isinstance(_zip, (str, pathlib.Path)):
- # If we have a path then we use the zip file.
- _zip = zipfile.ZipFile(_zip, 'a', zipfile.ZIP_DEFLATED)
- kwargs['zip'] = _zip
- createdZip = True
- else:
- createdZip = False
- # Path needs to be done in a special way if we are in a zip file.
- customPath = pathlib.Path(kwargs.get('customPath', ''))
- # Set the open command to be that of the zip file.
- _open = createZipOpen(_zip.open)
- # Zip files use w for writing in binary.
- mode = 'w'
- else:
- customPath = pathlib.Path(kwargs.get('customPath', '.')).absolute()
- mode = 'wb'
- _open = open
-
- fullFilename = customPath / filename
-
- if self.type is AttachmentType.DATA:
+ createdZip = True
+ try:
+ # ZipFile handling.
if _zip:
- name, ext = os.path.splitext(filename)
- nameList = _zip.namelist()
- if fullFilename in nameList:
- for i in range(2, 100):
- testName = customPath / f'{name} ({i}){ext}'
- if testName not in nameList:
- fullFilename = testName
- break
- else:
- # If we couldn't find one that didn't exist.
- raise FileExistsError(f'Could not create the specified file because it already exists ("{fullFilename}").')
+ # If we are doing a zip file, first check that we have been
+ # given a path.
+ if isinstance(_zip, (str, pathlib.Path)):
+ # If we have a path then we use the zip file.
+ _zip = zipfile.ZipFile(_zip, 'a', zipfile.ZIP_DEFLATED)
+ kwargs['zip'] = _zip
+ createdZip = True
+ else:
+ createdZip = False
+ # Path needs to be done in a special way if we are in a zip
+ # file.
+ customPath = pathlib.Path(kwargs.get('customPath', ''))
+ # Set the open command to be that of the zip file.
+ _open = createZipOpen(_zip.open)
+ # Zip files use w for writing in binary.
+ mode = 'w'
else:
- if fullFilename.exists():
- # Try to split the filename into a name and extention.
- name, ext = os.path.splitext(filename)
- # Try to add a number to it so that we can save without overwriting.
- for i in range(2, 100):
- testName = customPath / f'{name} ({i}){ext}'
- if not testName.exists():
- fullFilename = testName
- break
- else:
- # If we couldn't find one that didn't exist.
- raise FileExistsError(f'Could not create the specified file because it already exists ("{fullFilename}").')
-
- with _open(str(fullFilename), mode) as f:
- f.write(self.__data)
+ customPath = pathlib.Path(kwargs.get('customPath', '.')).absolute()
+ mode = 'wb'
+ _open = open
- # Close the ZipFile if this function created it.
- if _zip and createdZip:
- _zip.close()
+ fullFilename = self._handleFnc(_zip, filename, customPath, kwargs)
- return str(fullFilename)
- else:
- if kwargs.get('extractEmbedded', False):
+ if self.type is AttachmentType.DATA:
+ with _open(str(fullFilename), mode) as f:
+ f.write(self.__data)
+
+ return (SaveType.FILE, str(fullFilename))
+ else:
with _open(str(fullFilename), mode) as f:
# We just use the data we were given for this one.
f.write(self.__asBytes)
- else:
- self.saveEmbededMessage(**kwargs)
-
+ return (SaveType.FILE, str(fullFilename))
+ finally:
# Close the ZipFile if this function created it.
if _zip and createdZip:
_zip.close()
- return self.msg
-
- def saveEmbededMessage(self, **kwargs) -> None:
+ def saveEmbededMessage(self, **kwargs) -> constants.SAVE_TYPE:
"""
Seperate function from save to allow it to easily be overridden by a
subclass.
"""
- self.data.save(**kwargs)
+ return self.data.save(**kwargs)
@property
def asBytes(self) -> bytes:
@@ -196,6 +178,21 @@ def data(self) -> Union[bytes, MSGFile]:
"""
return self.__data
+ @property
+ def dataType(self) -> Optional[Type[type]]:
+ """
+ The class that the data type will use, if it can be retrieved.
+
+ This is a safe way to do type checking on data before knowing if it will
+ raise an exception. Returns None if no data will be returns or if an
+ exception will be raised.
+ """
+ try:
+ return None if self.data is None else self.data.__class__
+ except Exception:
+ # All exceptions that accessing data would cause should be silenced.
+ return None
+
@property
def emailMessage(self) -> email.message.Message:
"""
@@ -214,8 +211,13 @@ def mimetype(self) -> str:
def msg(self) -> MSGFile:
"""
The MSGFile instance this attachment belongs to.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
"""
- return self.__msg
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Attachment instance has been garbage collected.')
+ return msg
@property
def name(self) -> str:
@@ -228,7 +230,7 @@ def name(self) -> str:
shortFilename = name
@property
- def treePath(self) -> Tuple:
+ def treePath(self) -> List[weakref.ReferenceType]:
"""
A path, as a tuple of instances, needed to get to this instance through
the MSGFile-Attachment tree.
@@ -238,6 +240,6 @@ def treePath(self) -> Tuple:
@property
def type(self) -> AttachmentType:
"""
- The AttachmentType.
+ Returns an enum value that identifies the type of attachment.
"""
return AttachmentType.SIGNED if isinstance(self.__data, bytes) else AttachmentType.SIGNED_EMBEDDED
diff --git a/extract_msg/attachments/unsupported_att.py b/extract_msg/attachments/unsupported_att.py
new file mode 100644
index 00000000..97a756e9
--- /dev/null
+++ b/extract_msg/attachments/unsupported_att.py
@@ -0,0 +1,41 @@
+__all__ = [
+ 'UnsupportedAttachment',
+]
+
+
+from .. import constants
+from .attachment_base import AttachmentBase
+from ..enums import AttachmentType, SaveType
+
+
+class UnsupportedAttachment(AttachmentBase):
+ """
+ An attachment whose type is not currently supported.
+ """
+
+ def getFilename(self, **_) -> str:
+ raise NotImplementedError('Unsupported attachments cannot be saved.')
+
+ def save(self, **kwargs) -> constants.SAVE_TYPE:
+ """
+ Raises a NotImplementedError unless :param skipNotImplemented: is set to
+ True. If it is, returns a value that indicates no data was saved.
+ """
+ if not kwargs.get('skipNotImplemented', False):
+ return (SaveType.NONE, None)
+
+ raise NotImplementedError('Unsupported attachments cannot be saved.')
+
+ @property
+ def data(self) -> None:
+ """
+ Broken attachments have no data.
+ """
+ return None
+
+ @property
+ def type(self) -> AttachmentType:
+ """
+ Returns an enum value that identifies the type of attachment.
+ """
+ return AttachmentType.UNSUPPORTED
\ No newline at end of file
diff --git a/extract_msg/attachments/web_att.py b/extract_msg/attachments/web_att.py
new file mode 100644
index 00000000..669fe0fe
--- /dev/null
+++ b/extract_msg/attachments/web_att.py
@@ -0,0 +1,75 @@
+__all__ = [
+ 'WebAttachment',
+]
+
+
+import functools
+
+from typing import Optional
+
+from .. import constants
+from .attachment_base import AttachmentBase
+from ..enums import AttachmentPermissionType, AttachmentType, SaveType
+
+
+class WebAttachment(AttachmentBase):
+ """
+ An attachment that exists on the internet and not attached to the MSGFile
+ directly.
+ """
+
+ def getFilename(self) -> str:
+ raise NotImplementedError('Cannot get the filename of a web attachment.')
+
+ def save(self, **kwargs) -> constants.SAVE_TYPE:
+ """
+ Raises a NotImplementedError unless :param skipNotImplemented: is set to
+ True. If it is, returns a value that indicates no data was saved.
+ """
+ if kwargs.get('skipNotImplemented', False):
+ return (SaveType.NONE, None)
+
+ raise NotImplementedError('Web attachments cannot be saved.')
+
+ @property
+ def data(self) -> None:
+ """
+ The bytes making up the attachment data.
+ """
+ raise NotImplementedError('Cannot get the data of a web attachment.')
+
+ @functools.cached_property
+ def originalPermissionType(self) -> Optional[AttachmentPermissionType]:
+ """
+ The permission type data associated with a web reference attachment.
+ """
+ return self._getNamedAs('AttachmentOriginalPermissionType', constants.ps.PSETID_ATTACHMENT, AttachmentPermissionType)
+
+ @functools.cached_property
+ def permissionType(self) -> Optional[AttachmentPermissionType]:
+ """
+ The permission type data associated with a web reference attachment.
+ """
+ return self._getNamedAs('AttachmentPermissionType', constants.ps.PSETID_ATTACHMENT, AttachmentPermissionType)
+
+ @functools.cached_property
+ def providerName(self) -> Optional[str]:
+ """
+ The type of web service manipulating the attachment.
+ """
+ return self._getNamedAs('AttachmentProviderType', constants.ps.PSETID_ATTACHMENT)
+
+ @property
+ def type(self) -> AttachmentType:
+ """
+ Returns an enum value that identifies the type of attachment.
+ """
+ return AttachmentType.WEB
+
+ @property
+ def url(self) -> Optional[str]:
+ """
+ The url for the web attachment. If this is not set, that is probably an
+ error.
+ """
+ return self.longPathname
\ No newline at end of file
diff --git a/extract_msg/constants.py b/extract_msg/constants.py
deleted file mode 100644
index 9b049a90..00000000
--- a/extract_msg/constants.py
+++ /dev/null
@@ -1,805 +0,0 @@
-"""
-The constants used in extract_msg. If you modify any of these
-without explicit instruction to do so from one of the
-contributers, please do not complain about bugs.
-"""
-
-__all__ = [
- 'CODE_PAGES', 'DEFAULT_CLSID', 'FIXED_LENGTH_PROPS',
- 'FIXED_LENGTH_PROPS_STRING', 'HEADER_FORMAT', 'HEADER_FORMAT_TYPE',
- 'HEADER_FORMAT_VALUE_TYPE', 'KNOWN_CLASS_TYPES', 'KNOWN_FILE_FLAGS',
- 'MAINDOC', 'MULTIPLE_16_BYTES', 'MULTIPLE_16_BYTES_HEX',
- 'MULTIPLE_2_BYTES', 'MULTIPLE_2_BYTES_HEX', 'MULTIPLE_4_BYTES',
- 'MULTIPLE_4_BYTES_HEX', 'MULTIPLE_8_BYTES', 'MULTIPLE_8_BYTES_HEX',
- 'NEEDS_ARG', 'NULL_DATE', 'PROPERTIES', 'PSETID_ADDRESS', 'PSETID_AIRSYNC',
- 'PSETID_APPOINTMENT', 'PSETID_ATTACHMENT', 'PSETID_CALENDAR_ASSISTANT',
- 'PSETID_COMMON', 'PSETID_LOG', 'PSETID_MEETING', 'PSETID_MESSAGING',
- 'PSETID_NOTE', 'PSETID_POSTRSS', 'PSETID_SHARING', 'PSETID_TASK',
- 'PSETID_UNIFIEDMESSAGING', 'PSETID_XMLEXTRACTEDENTITIES',
- 'PS_INTERNET_HEADERS', 'PS_MAPI', 'PS_PUBLIC_STRINGS', 'PTYPES',
- 'PYTPFLOATINGTIME_START', 'RE_BIN', 'RE_HTML_BODY_START',
- 'RE_HTML_SAN_SPACE', 'RE_INVALID_FILENAME_CHARACTERS',
- 'RE_INVALID_OLE_PATH', 'RE_RTF_ENC_BODY_START', 'ST1', 'ST2', 'ST3',
- 'STF32', 'STF64', 'STFIX', 'STI16', 'STI32', 'STI64', 'STI8', 'STMF32',
- 'STMF64', 'STMI16', 'STMI32', 'STMI64', 'STNP_ENT', 'STNP_NAM', 'STPEID',
- 'STUI32', 'STVAR', 'ST_BC_FIELD_INFO', 'ST_BC_HEAD', 'ST_BE_F32',
- 'ST_BE_F64', 'ST_BE_I16', 'ST_BE_I32', 'ST_BE_I64', 'ST_BE_I8',
- 'ST_BE_UI16', 'ST_BE_UI32', 'ST_BE_UI64', 'ST_BE_UI8', 'ST_CF_DIR_ENTRY',
- 'ST_DATA_UI16', 'ST_DATA_UI32', 'ST_DATA_UI8', 'ST_GUID', 'ST_LE_F32',
- 'ST_LE_F64', 'ST_LE_I16', 'ST_LE_I32', 'ST_LE_I64', 'ST_LE_I8',
- 'ST_LE_UI16', 'ST_LE_UI32', 'ST_LE_UI64', 'ST_LE_UI8', 'ST_SYSTEMTIME',
- 'ST_TZ', 'VARIABLE_LENGTH_PROPS', 'VARIABLE_LENGTH_PROPS_STRING',
-]
-
-
-import datetime
-import re
-import struct
-
-import ebcdic
-
-from typing import Dict, Tuple, Union
-
-
-# DEFINE CONSTANTS
-# WARNING DO NOT CHANGE ANY OF THESE VALUES UNLESS YOU KNOW
-# WHAT YOU ARE DOING! FAILURE TO FOLLOW THIS INSTRUCTION
-# CAN AND WILL BREAK THIS SCRIPT!
-
-# Typing Constants.
-HEADER_FORMAT_VALUE_TYPE = Union[str, Tuple[Union[str, None], bool], None]
-# Basically a dict of HEADER_FORMAT_TYPE and dicts containing them.
-HEADER_FORMAT_TYPE = Dict[str, Union[HEADER_FORMAT_VALUE_TYPE, Dict[str, HEADER_FORMAT_VALUE_TYPE]]]
-
-# Regular expresion constants.
-RE_INVALID_FILENAME_CHARACTERS = re.compile(r'[\\/:*?"<>|]')
-# Regular expression to find sections of spaces for htmlSanitize.
-RE_HTML_SAN_SPACE = re.compile(' +')
-# Regular expression to find the start of the html body.
-RE_HTML_BODY_START = re.compile(b']*>')
-# Regular expression to find the start of the html body in encapsulated RTF.
-# This is used for one of the pattern types that makes life easy.
-RE_RTF_ENC_BODY_START = re.compile(br'\{\\\*\\htmltag[0-9]* ?]*>\}')
-# This is used in the workaround for decoding issues in RTFDE. We find `\bin`
-# sections and try to remove all of them to help with the decoding.
-RE_BIN = re.compile(br'\\bin([0-9]+) ?')
-# Used in the vaildation of OLE paths. Any of these characters in a name make it
-# invalid.
-RE_INVALID_OLE_PATH = re.compile(r'[:/\\!]')
-
-FIXED_LENGTH_PROPS = (
- 0x0000,
- 0x0001,
- 0x0002,
- 0x0003,
- 0x0004,
- 0x0005,
- 0x0006,
- 0x0007,
- 0x000A,
- 0x000B,
- 0x0014,
- 0x0040,
- 0x0048,
-)
-
-FIXED_LENGTH_PROPS_STRING = (
- '0000',
- '0001',
- '0002',
- '0003',
- '0004',
- '0005',
- '0006',
- '0007',
- '000A',
- '000B',
- '0014',
- '0040',
- '0048',
-)
-
-VARIABLE_LENGTH_PROPS = (
- 0x000D,
- 0x001E,
- 0x001F,
- 0x00FB,
- 0x00FD,
- 0x00FE,
- 0X0102,
- 0x1002,
- 0x1003,
- 0x1004,
- 0x1005,
- 0x1006,
- 0x1007,
- 0x1014,
- 0x101E,
- 0x101F,
- 0x1040,
- 0x1048,
- 0x1102,
-)
-
-VARIABLE_LENGTH_PROPS_STRING = (
- '000D',
- '001E',
- '001F',
- '00FB',
- '00FD',
- '00FE',
- '0102',
- '1002',
- '1003',
- '1004',
- '1005',
- '1006',
- '1007',
- '1014',
- '101E',
- '101F',
- '1040',
- '1048',
- '1102',
-)
-
-# Multiple type properties that take up 2 bytes
-MULTIPLE_2_BYTES = (
- '1002',
-)
-
-MULTIPLE_2_BYTES_HEX = (
- 0x1002,
-)
-
-# Multiple type properties that take up 4 bytes
-MULTIPLE_4_BYTES = (
- '1003',
- '1004',
-)
-
-MULTIPLE_4_BYTES_HEX = (
- 0x1003,
- 0x1004,
-)
-
-# Multiple type properties that take up 4 bytes
-MULTIPLE_8_BYTES = (
- '1005',
- '1007',
- '1014',
- '1040',
-)
-
-MULTIPLE_8_BYTES_HEX = (
- 0x1005,
- 0x1007,
- 0x1014,
- 0x1040,
-)
-
-# Multiple type properties that take up 4 bytes
-MULTIPLE_16_BYTES = (
- '1048',
-)
-
-MULTIPLE_16_BYTES_HEX = (
- 0x1048,
-)
-
-
-# Used to format the header for saving only the header.
-HEADER_FORMAT = """From: {From}
-To: {To}
-Cc: {Cc}
-Bcc: {Bcc}
-Subject: {subject}
-Date: {Date}
-Message-ID: {Message-Id}
-"""
-
-
-KNOWN_CLASS_TYPES = (
- 'ipm.activity',
- 'ipm.appointment', # [MS-OXOCAL]
- 'ipm.contact', # [MS-OXOCNTC]
- 'ipm.configuration', # [MS-OXOCFG]
- 'ipm.distlist',
- 'ipm.document',
- 'ipm.ole.class',
- 'ipm.outlook.recall',
- 'ipm.note',
- 'ipm.post',
- 'ipm.stickynote',
- 'ipm.recall.report',
- 'ipm.remote',
- 'ipm.report',
- 'ipm.resend',
- 'ipm.schedule',
- 'ipm.task',
- 'ipm.taskrequest',
- 'report',
-)
-
-# This is a dictionary matching the code page number to it's encoding name.
-# The list used to make this can be found here:
-# https://docs.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
-### TODO:
-# Many of these code pages are not supported by Python. As such, we should
-# really implement them ourselves to make sure that if someone wants to use an
-# msg file with one of those encodings, they are able to. Perhaps we should
-# create a seperate module for that?
-# Code pages that currently don't have a supported encoding will be preceded by
-# `# UNSUPPORTED`.
-# For some of these, it is also possible that the name we are trying to find
-# them with is not known to Python. I have already confirmed this for a few of
-# them, and adjusted their names to ones that python would recognize. It is
-# Possible I missed a few.
-CODE_PAGES = {
- 37: 'IBM037', # IBM EBCDIC US-Canada
- 437: 'IBM437', # OEM United States
- 500: 'IBM500', # IBM EBCDIC International
- 708: 'ASMO-708', # Arabic (ASMO 708)
- # UNSUPPORTED.
- 709: '', # Arabic (ASMO-449+, BCON V4)
- # UNSUPPORTED.
- 710: '', # Arabic - Transparent Arabic
- # UNSUPPORTED.
- 720: 'DOS-720', # Arabic (Transparent ASMO); Arabic (DOS)
- 737: 'cp737', # OEM Greek (formerly 437G); Greek (DOS)
- 775: 'ibm775', # OEM Baltic; Baltic (DOS)
- 850: 'ibm850', # OEM Multilingual Latin 1; Western European (DOS)
- 852: 'ibm852', # OEM Latin 2; Central European (DOS)
- 855: 'IBM855', # OEM Cyrillic (primarily Russian)
- 857: 'ibm857', # OEM Turkish; Turkish (DOS)
- 858: 'cp858', # OEM Multilingual Latin 1 + Euro symbol
- 860: 'IBM860', # OEM Portuguese; Portuguese (DOS)
- 861: 'ibm861', # OEM Icelandic; Icelandic (DOS)
- 862: 'cp862', # OEM Hebrew; Hebrew (DOS)
- 863: 'IBM863', # OEM French Canadian; French Canadian (DOS)
- 864: 'IBM864', # OEM Arabic; Arabic (864)
- 865: 'IBM865', # OEM Nordic; Nordic (DOS)
- 866: 'cp866', # OEM Russian; Cyrillic (DOS)
- 869: 'ibm869', # OEM Modern Greek; Greek, Modern (DOS)
- 870: 'cp870', # IBM870 # IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2
- # UNSUPPORTED.
- 874: 'windows-874', # ANSI/OEM Thai (ISO 8859-11); Thai (Windows)
- 875: 'cp875', # IBM EBCDIC Greek Modern
- 932: 'shift_jis', # ANSI/OEM Japanese; Japanese (Shift-JIS)
- 936: 'gb2312', # ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)
- 949: 'ks_c_5601-1987', # ANSI/OEM Korean (Unified Hangul Code)
- 950: 'big5', # ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5)
- 1026: 'IBM1026', # IBM EBCDIC Turkish (Latin 5)
- 1047: 'cp1047', # IBM EBCDIC Latin 1/Open System
- 1140: 'cp1140', # IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro)
- 1141: 'cp1141', # IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro)
- 1142: 'cp1142', # IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro)
- 1143: 'cp1143', # IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro)
- 1144: 'cp1144', # IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro)
- 1145: 'cp1145', # IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro)
- 1146: 'cp1146', # IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro)
- 1147: 'cp1147', # IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro)
- 1148: 'cp1148ms', # IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro)
- 1149: 'cp1149', # IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro)
- 1200: 'utf-16-le', # Unicode UTF-16, little endian byte order (BMP of ISO 10646); available only to managed applications
- 1201: 'utf-16-be', # Unicode UTF-16, big endian byte order; available only to managed applications
- 1250: 'windows-1250', # ANSI Central European; Central European (Windows)
- 1251: 'windows-1251', # ANSI Cyrillic; Cyrillic (Windows)
- 1252: 'windows-1252', # ANSI Latin 1; Western European (Windows)
- 1253: 'windows-1253', # ANSI Greek; Greek (Windows)
- 1254: 'windows-1254', # ANSI Turkish; Turkish (Windows)
- 1255: 'windows-1255', # ANSI Hebrew; Hebrew (Windows)
- 1256: 'windows-1256', # ANSI Arabic; Arabic (Windows)
- 1257: 'windows-1257', # ANSI Baltic; Baltic (Windows)
- 1258: 'windows-1258', # ANSI/OEM Vietnamese; Vietnamese (Windows)
- 1361: 'Johab', # Korean (Johab)
- 10000: 'macintosh', # MAC Roman; Western European (Mac)
- 10001: 'x-mac-japanese', # Japanese (Mac)
- # UNSUPPORTED.
- 10002: 'x-mac-chinesetrad', # MAC Traditional Chinese (Big5); Chinese Traditional (Mac)
- 10003: 'x-mac-korean', # Korean (Mac)
- # UNSUPPORTED.
- 10004: 'x-mac-arabic', # Arabic (Mac)
- # UNSUPPORTED.
- 10005: 'x-mac-hebrew', # Hebrew (Mac)
- # UNSUPPORTED.
- 10006: 'x-mac-greek', # Greek (Mac)
- # UNSUPPORTED.
- 10007: 'x-mac-cyrillic', # Cyrillic (Mac)
- # UNSUPPORTED.
- 10008: 'x-mac-chinesesimp', # MAC Simplified Chinese (GB 2312); Chinese Simplified (Mac)
- # UNSUPPORTED.
- 10010: 'x-mac-romanian', # Romanian (Mac)
- # UNSUPPORTED.
- 10017: 'x-mac-ukrainian', # Ukrainian (Mac)
- # UNSUPPORTED.
- 10021: 'x-mac-thai', # Thai (Mac)
- # UNSUPPORTED.
- 10029: 'x-mac-ce', # MAC Latin 2; Central European (Mac)
- # UNSUPPORTED.
- 10079: 'x-mac-icelandic', # Icelandic (Mac)
- # UNSUPPORTED.
- 10081: 'x-mac-turkish', # Turkish (Mac)
- # UNSUPPORTED.
- 10082: 'x-mac-croatian', # Croatian (Mac)
- 12000: 'utf-32', # Unicode UTF-32, little endian byte order; available only to managed applications
- 12001: 'utf-32BE', # Unicode UTF-32, big endian byte order; available only to managed applications
- # UNSUPPORTED.
- 20000: 'x-Chinese_CNS', # CNS Taiwan; Chinese Traditional (CNS)
- # UNSUPPORTED.
- 20001: 'x-cp20001', # TCA Taiwan
- # UNSUPPORTED.
- 20002: 'x_Chinese-Eten', # Eten Taiwan; Chinese Traditional (Eten)
- # UNSUPPORTED.
- 20003: 'x-cp20003', # IBM5550 Taiwan
- # UNSUPPORTED.
- 20004: 'x-cp20004', # TeleText Taiwan
- # UNSUPPORTED.
- 20005: 'x-cp20005', # Wang Taiwan
- # UNSUPPORTED.
- 20105: 'x-IA5', # IA5 (IRV International Alphabet No. 5, 7-bit); Western European (IA5)
- # UNSUPPORTED.
- 20106: 'x-IA5-German', # IA5 German (7-bit)
- # UNSUPPORTED.
- 20107: 'x-IA5-Swedish', # IA5 Swedish (7-bit)
- # UNSUPPORTED.
- 20108: 'x-IA5-Norwegian', # IA5 Norwegian (7-bit)
- 20127: 'us-ascii', # US-ASCII (7-bit)
- # UNSUPPORTED.
- 20261: 'x-cp20261', # T.61
- # UNSUPPORTED.
- 20269: 'x-cp20269', # ISO 6937 Non-Spacing Accent
- 20273: 'IBM273', # IBM EBCDIC Germany
- 20277: 'cp277', # IBM EBCDIC Denmark-Norway
- 20278: 'cp278', # IBM EBCDIC Finland-Sweden
- 20280: 'cp280', # IBM EBCDIC Italy
- 20284: 'cp284', # IBM EBCDIC Latin America-Spain
- 20285: 'cp285', # IBM EBCDIC United Kingdom
- 20290: 'cp290', # IBM EBCDIC Japanese Katakana Extended
- 20297: 'cp297', # IBM EBCDIC France
- 20420: 'cp420', # IBM EBCDIC Arabic
- # UNSUPPORTED.
- 20423: 'IBM423', # IBM EBCDIC Greek
- 20424: 'IBM424', # IBM EBCDIC Hebrew
- 20833: 'cp833', # IBM EBCDIC Korean Extended
- 20838: 'cp838', # IBM EBCDIC Thai
- 20866: 'koi8-r', # Russian (KOI8-R); Cyrillic (KOI8-R)
- 20871: 'cp871', # IBM EBCDIC Icelandic
- # UNSUPPORTED.
- 20880: 'IBM880', # IBM EBCDIC Cyrillic Russian
- # UNSUPPORTED.
- 20905: 'IBM905', # IBM EBCDIC Turkish
- # UNSUPPORTED.
- 20924: 'IBM00924', # IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)
- 20932: 'EUC-JP', # Japanese (JIS 0208-1990 and 0212-1990)
- # UNSUPPORTED.
- 20936: 'x-cp20936', # Simplified Chinese (GB2312); Chinese Simplified (GB2312-80)
- # UNSUPPORTED.
- 20949: 'x-cp20949', # Korean Wansung
- 21025: 'cp1025', # IBM EBCDIC Cyrillic Serbian-Bulgarian
- # UNSUPPORTED.
- 21027: '', # (deprecated)
- 21866: 'koi8-u', # Ukrainian (KOI8-U); Cyrillic (KOI8-U)
- 28591: 'iso-8859-1', # ISO 8859-1 Latin 1; Western European (ISO)
- 28592: 'iso-8859-2', # ISO 8859-2 Central European; Central European (ISO)
- 28593: 'iso-8859-3', # ISO 8859-3 Latin 3
- 28594: 'iso-8859-4', # ISO 8859-4 Baltic
- 28595: 'iso-8859-5', # ISO 8859-5 Cyrillic
- 28596: 'iso-8859-6', # ISO 8859-6 Arabic
- 28597: 'iso-8859-7', # ISO 8859-7 Greek
- 28598: 'iso-8859-8', # ISO 8859-8 Hebrew; Hebrew (ISO-Visual)
- 28599: 'iso-8859-9', # ISO 8859-9 Turkish
- 28603: 'iso-8859-13', # ISO 8859-13 Estonian
- 28605: 'iso-8859-15', # ISO 8859-15 Latin 9
- # UNSUPPORTED.
- 29001: 'x-Europa', # Europa 3
- # UNSUPPORTED.
- 38598: 'iso-8859-8-i', # ISO 8859-8 Hebrew; Hebrew (ISO-Logical)
- 50220: 'iso-2022-jp', # ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS)
- 50221: 'csISO2022JP', # ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana)
- 50222: 'iso-2022-jp', # ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI)
- 50225: 'iso-2022-kr', # ISO 2022 Korean
- # UNSUPPORTED.
- 50227: 'x-cp50227', # ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022)
- # UNSUPPORTED.
- 50229: '', # ISO 2022 Traditional Chinese
- # UNSUPPORTED.
- 50930: '', # EBCDIC Japanese (Katakana) Extended
- # UNSUPPORTED.
- 50931: '', # EBCDIC US-Canada and Japanese
- # UNSUPPORTED.
- 50933: '', # EBCDIC Korean Extended and Korean
- # UNSUPPORTED.
- 50935: '', # EBCDIC Simplified Chinese Extended and Simplified Chinese
- # UNSUPPORTED.
- 50936: '', # EBCDIC Simplified Chinese
- # UNSUPPORTED.
- 50937: '', # EBCDIC US-Canada and Traditional Chinese
- # UNSUPPORTED.
- 50939: '', # EBCDIC Japanese (Latin) Extended and Japanese
- 51932: 'euc-jp', # EUC Japanese
- 51936: 'EUC-CN', # EUC Simplified Chinese; Chinese Simplified (EUC)
- 51949: 'euc-kr', # EUC Korean
- # UNSUPPORTED.
- 51950: '', # EUC Traditional Chinese
- 52936: 'hz-gb-2312', # HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)
- 54936: 'GB18030', # Windows XP and later: GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030)
- # UNSUPPORTED.
- 57002: 'x-iscii-de', # ISCII Devanagari
- # UNSUPPORTED.
- 57003: 'x-iscii-be', # ISCII Bangla
- # UNSUPPORTED.
- 57004: 'x-iscii-ta', # ISCII Tamil
- # UNSUPPORTED.
- 57005: 'x-iscii-te', # ISCII Telugu
- # UNSUPPORTED.
- 57006: 'x-iscii-as', # ISCII Assamese
- # UNSUPPORTED.
- 57007: 'x-iscii-or', # ISCII Odia
- # UNSUPPORTED.
- 57008: 'x-iscii-ka', # ISCII Kannada
- # UNSUPPORTED.
- 57009: 'x-iscii-ma', # ISCII Malayalam
- # UNSUPPORTED.
- 57010: 'x-iscii-gu', # ISCII Gujarati
- # UNSUPPORTED.
- 57011: 'x-iscii-pa', # ISCII Punjabi
- 65000: 'utf-7', # Unicode (UTF-7)
- 65001: 'utf-8', # Unicode (UTF-8)
-}
-
-PYTPFLOATINGTIME_START = datetime.datetime(1899, 12, 30)
-NULL_DATE = datetime.datetime(4500, 8, 31, 23, 59)
-
-# Constants used for argparse stuff
-KNOWN_FILE_FLAGS = (
- '--out-name',
-)
-NEEDS_ARG = (
- '--out-name',
-)
-MAINDOC = "extract_msg:\n\tExtracts emails and attachments saved in Microsoft Outlook's .msg files.\n\n" \
- "https://github.com/TeamMsgExtractor/msg-extractor"
-
-# Default class ID for the root entry for OleWriter. This should be
-# referencing Outlook if I understand it correctly.
-DEFAULT_CLSID = b'\x0b\r\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00F'
-
-# Define pre-compiled structs to make unpacking slightly faster.
-# General structs.
-ST1 = struct.Struct('<8x4I')
-ST2 = struct.Struct('b')
-ST_BE_I16 = struct.Struct('>h')
-ST_BE_I32 = struct.Struct('>i')
-ST_BE_I64 = struct.Struct('>q')
-ST_BE_UI8 = struct.Struct('>B')
-ST_BE_UI16 = struct.Struct('>H')
-ST_BE_UI32 = struct.Struct('>I')
-ST_BE_UI64 = struct.Struct('>Q')
-ST_BE_F32 = struct.Struct('>f')
-ST_BE_F64 = struct.Struct('>d')
-
-PTYPES = {
- 0x0000: 'PtypUnspecified',
- 0x0001: 'PtypNull',
- 0x0002: 'PtypInteger16', # Signed short
- 0x0003: 'PtypInteger32', # Signed int
- 0x0004: 'PtypFloating32', # Float
- 0x0005: 'PtypFloating64', # Double
- 0x0006: 'PtypCurrency',
- 0x0007: 'PtypFloatingTime',
- 0x000A: 'PtypErrorCode',
- 0x000B: 'PtypBoolean',
- 0x000D: 'PtypObject/PtypEmbeddedTable/Storage',
- 0x0014: 'PtypInteger64', # Signed longlong
- 0x001E: 'PtypString8',
- 0x001F: 'PtypString',
- 0x0040: 'PtypTime', # Use filetimeToUtc to convert to unix time stamp
- 0x0048: 'PtypGuid',
- 0x00FB: 'PtypServerId',
- 0x00FD: 'PtypRestriction',
- 0x00FE: 'PtypRuleAction',
- 0x0102: 'PtypBinary',
- 0x1002: 'PtypMultipleInteger16',
- 0x1003: 'PtypMultipleInteger32',
- 0x1004: 'PtypMultipleFloating32',
- 0x1005: 'PtypMultipleFloating64',
- 0x1006: 'PtypMultipleCurrency',
- 0x1007: 'PtypMultipleFloatingTime',
- 0x1014: 'PtypMultipleInteger64',
- 0x101E: 'PtypMultipleString8',
- 0x101F: 'PtypMultipleString',
- 0x1040: 'PtypMultipleTime',
- 0x1048: 'PtypMultipleGuid',
- 0x1102: 'PtypMultipleBinary',
-}
-
-# This property information was sourced from
-# http://www.fileformat.info/format/outlookmsg/index.htm
-# on 2013-07-22.
-# It was extended by The Elemental of Destruction on 2018-10-12.
-PROPERTIES = {
- '00010102': 'Template data',
- '0002000B': 'Alternate recipient allowed',
- '0004001F': 'Auto forward comment',
- '00040102': 'Script data',
- '0005000B': 'Auto forwarded',
- '000F000F': 'Deferred delivery time',
- '00100040': 'Deliver time',
- '00150040': 'Expiry time',
- '00170003': 'Importance',
- '001A001F': 'Message class',
- '0023001F': 'Originator delivery report requested',
- '00250102': 'Parent key',
- '00260003': 'Priority',
- '0029000B': 'Read receipt requested',
- '002A0040': 'Receipt time',
- '002B000B': 'Recipient reassignment prohibited',
- '002E0003': 'Original sensitivity',
- '00300040': 'Reply time',
- '00310102': 'Report tag',
- '00320040': 'Report time',
- '00360003': 'Sensitivity',
- '0037001F': 'Subject',
- '00390040': 'Client Submit Time',
- '003A001F': '',
- '003B0102': '',
- '003D001F': 'Subject prefix',
- '003F0102': '',
- '0040001F': 'Received by name',
- '00410102': '',
- '0042001F': 'Sent repr name',
- '00430102': '',
- '0044001F': 'Rcvd repr name',
- '00450102': '',
- '0046001F': '',
- '00470102': '',
- '0049001F': '',
- '004B001F': '',
- '004C0102': '',
- '004D001F': 'Org author name',
- '004E0040': '',
- '004F0102': '',
- '0050001F': 'Reply rcipnt names',
- '00510102': '',
- '00520102': '',
- '00530102': '',
- '00540102': '',
- '00550040': '',
- '0057000B': '',
- '0058000B': '',
- '0059000B': '',
- '005A001F': 'Org sender name',
- '005B0102': '',
- '005C0102': '',
- '005D001F': '',
- '005E0102': '',
- '005F0102': '',
- '00600040': '',
- '00610040': '',
- '00620003': '',
- '0063000B': '',
- '0064001F': 'Sent repr adrtype',
- '0065001F': 'Sent repr email',
- '0066001F': '',
- '00670102': '',
- '0068001F': '',
- '0069001F': '',
- '0070001F': 'Topic',
- '00710102': '',
- '0072001F': '',
- '0073001F': '',
- '0074001F': '',
- '0075001F': 'Rcvd by adrtype',
- '0076001F': 'Rcvd by email',
- '0077001F': 'Repr adrtype',
- '0078001F': 'Repr email',
- '007D001F': 'Message header',
- '007F0102': '',
- '0080001F': '',
- '0081001F': '',
- '08070003': '',
- '0809001F': '',
- '0C040003': '',
- '0C050003': '',
- '0C06000B': '',
- '0C08000B': '',
- '0C150003': '',
- '0C17000B': '',
- '0C190102': '',
- '0C1A001F': 'Sender name',
- '0C1B001F': '',
- '0C1D0102': '',
- '0C1E001F': 'Sender adr type',
- '0C1F001F': 'Sender email',
- '0C200003': '',
- '0C21001F': '',
- '0E01000B': '',
- '0E02001F': 'Display BCC',
- '0E03001F': 'Display CC',
- '0E04001F': 'Display To',
- '0E060040': '',
- '0E070003': '',
- '0E080003': '',
- '0E080014': '',
- '0E090102': '',
- '0E0F000B': '',
- '0E12000D': '',
- '0E13000D': '',
- '0E170003': '',
- '0E1B000B': '',
- '0E1D001F': 'Subject (normalized)',
- '0E1F000B': '',
- '0E200003': '',
- '0E210003': '',
- '0E28001F': 'Recvd account1 (uncertain)',
- '0E29001F': 'Recvd account2 (uncertain)',
- '1000001F': 'Message body',
- '1008': 'RTF sync body tag', # Where did this come from ??? It's not listed in the docs
- '10090102': 'Compressed RTF body',
- '1013001F': 'HTML body',
- '1035001F': 'Message ID (uncertain)',
- '1046001F': 'Sender email (uncertain)',
- '3001001F': 'Display name',
- '3002001F': 'Address type',
- '3003001F': 'Email address',
- '30070040': 'Creation date',
- '39FE001F': '7-bit email (uncertain)',
- '39FF001F': '7-bit display name',
-
- # Attachments (37xx)
- '37010102': 'Attachment data',
- '37020102': '',
- '3703001F': 'Attachment extension',
- '3704001F': 'Attachment short filename',
- '37050003': 'Attachment attach method',
- '3707001F': 'Attachment long filename',
- '370E001F': 'Attachment mime tag',
- '3712001F': 'Attachment ID (uncertain)',
-
- # Address book (3Axx):
- '3A00001F': 'Account',
- '3A02001F': 'Callback phone no',
- '3A05001F': 'Generation',
- '3A06001F': 'Given name',
- '3A08001F': 'Business phone',
- '3A09001F': 'Home phone',
- '3A0A001F': 'Initials',
- '3A0B001F': 'Keyword',
- '3A0C001F': 'Language',
- '3A0D001F': 'Location',
- '3A11001F': 'Surname',
- '3A15001F': 'Postal address',
- '3A16001F': 'Company name',
- '3A17001F': 'Title',
- '3A18001F': 'Department',
- '3A19001F': 'Office location',
- '3A1A001F': 'Primary phone',
- '3A1B101F': 'Business phone 2',
- '3A1C001F': 'Mobile phone',
- '3A1D001F': 'Radio phone no',
- '3A1E001F': 'Car phone no',
- '3A1F001F': 'Other phone',
- '3A20001F': 'Transmit dispname',
- '3A21001F': 'Pager',
- '3A220102': 'User certificate',
- '3A23001F': 'Primary Fax',
- '3A24001F': 'Business Fax',
- '3A25001F': 'Home Fax',
- '3A26001F': 'Country',
- '3A27001F': 'Locality',
- '3A28001F': 'State/Province',
- '3A29001F': 'Street address',
- '3A2A001F': 'Postal Code',
- '3A2B001F': 'Post Office Box',
- '3A2C001F': 'Telex',
- '3A2D001F': 'ISDN',
- '3A2E001F': 'Assistant phone',
- '3A2F001F': 'Home phone 2',
- '3A30001F': 'Assistant',
- '3A44001F': 'Middle name',
- '3A45001F': 'Dispname prefix',
- '3A46001F': 'Profession',
- '3A47001F': '',
- '3A48001F': 'Spouse name',
- '3A4B001F': 'TTYTTD radio phone',
- '3A4C001F': 'FTP site',
- '3A4E001F': 'Manager name',
- '3A4F001F': 'Nickname',
- '3A51001F': 'Business homepage',
- '3A57001F': 'Company main phone',
- '3A58101F': 'Childrens names',
- '3A59001F': 'Home City',
- '3A5A001F': 'Home Country',
- '3A5B001F': 'Home Postal Code',
- '3A5C001F': 'Home State/Provnce',
- '3A5D001F': 'Home Street',
- '3A5F001F': 'Other adr City',
- '3A60': 'Other adr Country',
- '3A61': 'Other adr PostCode',
- '3A62': 'Other adr Province',
- '3A63': 'Other adr Street',
- '3A64': 'Other adr PO box',
-
- '3FF7': 'Server (uncertain)',
- '3FF8': 'Creator1 (uncertain)',
- '3FFA': 'Creator2 (uncertain)',
- '3FFC': 'To email (uncertain)',
- '403D': 'To adrtype (uncertain)',
- '403E': 'To email (uncertain)',
- '5FF6': 'To (uncertain)',
-}
-
-PS_MAPI = '{00020328-0000-0000-C000-000000000046}'
-PS_PUBLIC_STRINGS = '{00020329-0000-0000-C000-000000000046}'
-PSETID_COMMON = '{00062008-0000-0000-C000-000000000046}'
-PSETID_ADDRESS = '{00062004-0000-0000-C000-000000000046}'
-PS_INTERNET_HEADERS = '{00020386-0000-0000-C000-000000000046}'
-PSETID_APPOINTMENT = '{00062002-0000-0000-C000-000000000046}'
-PSETID_MEETING = '{6ED8DA90-450B-101B-98DA-00AA003F1305}'
-PSETID_LOG = '{0006200A-0000-0000-C000-000000000046}'
-PSETID_MESSAGING = '{41F28F13-83F4-4114-A584-EEDB5A6B0BFF}'
-PSETID_NOTE = '{0006200E-0000-0000-C000-000000000046}'
-PSETID_POSTRSS = '{00062041-0000-0000-C000-000000000046}'
-PSETID_TASK = '{00062003-0000-0000-C000-000000000046}'
-PSETID_UNIFIEDMESSAGING = '{4442858E-A9E3-4E80-B900-317A210CC15B}'
-PSETID_AIRSYNC = '{71035549-0739-4DCB-9163-00F0580DBBDF}'
-PSETID_SHARING = '{00062040-0000-0000-C000-000000000046}'
-PSETID_XMLEXTRACTEDENTITIES = '{23239608-685D-4732-9C55-4C95CB4E8E33}'
-PSETID_ATTACHMENT = '{96357F7F-59E1-47D0-99A7-46515C183B54}'
-PSETID_CALENDAR_ASSISTANT = '{11000E07-B51B-40D6-AF21-CAA85EDAB1D0}'
-
-# END CONSTANTS
diff --git a/extract_msg/constants/__init__.py b/extract_msg/constants/__init__.py
new file mode 100644
index 00000000..6a908f87
--- /dev/null
+++ b/extract_msg/constants/__init__.py
@@ -0,0 +1,274 @@
+"""
+The constants used in extract_msg. If you modify any of these without explicit
+instruction to do so from one of the contributers, please do not complain about
+bugs.
+"""
+
+__all__ = [
+ # Modules.
+ 'ps',
+ 're',
+ 'st',
+
+ # Constants.
+ 'DEFAULT_CLSID',
+ 'FIXED_LENGTH_PROPS',
+ 'FIXED_LENGTH_PROPS_STRING',
+ 'HEADER_FORMAT',
+ 'HEADER_FORMAT_TYPE',
+ 'HEADER_FORMAT_VALUE_TYPE',
+ 'KNOWN_CLASS_TYPES',
+ 'KNOWN_FILE_FLAGS',
+ 'MAINDOC',
+ 'MULTIPLE_16_BYTES',
+ 'MULTIPLE_16_BYTES_HEX',
+ 'MULTIPLE_2_BYTES',
+ 'MULTIPLE_2_BYTES_HEX',
+ 'MULTIPLE_4_BYTES',
+ 'MULTIPLE_4_BYTES_HEX',
+ 'MULTIPLE_8_BYTES',
+ 'MULTIPLE_8_BYTES_HEX',
+ 'NEEDS_ARG',
+ 'NULL_DATE',
+ 'PTYPES',
+ 'PYTPFLOATINGTIME_START',
+ 'REFUSED_CLASS_TYPES',
+ 'REPOSITORY_URL',
+ 'SAVE_TYPE',
+ 'VARIABLE_LENGTH_PROPS',
+ 'VARIABLE_LENGTH_PROPS_STRING',
+]
+
+
+import datetime
+
+from typing import Dict, List, Tuple, Union
+
+from . import ps, re, st
+from ..enums import SaveType
+
+
+# Typing Constants.
+HEADER_FORMAT_VALUE_TYPE = Union[str, Tuple[Union[str, None], bool], None]
+# Basically a dict of HEADER_FORMAT_TYPE and dicts containing them.
+HEADER_FORMAT_TYPE = Dict[str, Union[HEADER_FORMAT_VALUE_TYPE, Dict[str, HEADER_FORMAT_VALUE_TYPE]]]
+SAVE_TYPE = Tuple[SaveType, Union[List[str], str, None]]
+
+
+
+FIXED_LENGTH_PROPS = (
+ 0x0000,
+ 0x0001,
+ 0x0002,
+ 0x0003,
+ 0x0004,
+ 0x0005,
+ 0x0006,
+ 0x0007,
+ 0x000A,
+ 0x000B,
+ 0x0014,
+ 0x0040,
+ 0x0048,
+)
+
+FIXED_LENGTH_PROPS_STRING = (
+ '0000',
+ '0001',
+ '0002',
+ '0003',
+ '0004',
+ '0005',
+ '0006',
+ '0007',
+ '000A',
+ '000B',
+ '0014',
+ '0040',
+ '0048',
+)
+
+VARIABLE_LENGTH_PROPS = (
+ 0x000D,
+ 0x001E,
+ 0x001F,
+ 0x00FB,
+ 0x00FD,
+ 0x00FE,
+ 0X0102,
+ 0x1002,
+ 0x1003,
+ 0x1004,
+ 0x1005,
+ 0x1006,
+ 0x1007,
+ 0x1014,
+ 0x101E,
+ 0x101F,
+ 0x1040,
+ 0x1048,
+ 0x1102,
+)
+
+VARIABLE_LENGTH_PROPS_STRING = (
+ '000D',
+ '001E',
+ '001F',
+ '00FB',
+ '00FD',
+ '00FE',
+ '0102',
+ '1002',
+ '1003',
+ '1004',
+ '1005',
+ '1006',
+ '1007',
+ '1014',
+ '101E',
+ '101F',
+ '1040',
+ '1048',
+ '1102',
+)
+
+# Multiple type properties that take up 2 bytes.
+MULTIPLE_2_BYTES = (
+ '1002',
+)
+
+MULTIPLE_2_BYTES_HEX = (
+ 0x1002,
+)
+
+# Multiple type properties that take up 4 bytes.
+MULTIPLE_4_BYTES = (
+ '1003',
+ '1004',
+)
+
+MULTIPLE_4_BYTES_HEX = (
+ 0x1003,
+ 0x1004,
+)
+
+# Multiple type properties that take up 8 bytes.
+MULTIPLE_8_BYTES = (
+ '1005',
+ '1007',
+ '1014',
+ '1040',
+)
+
+MULTIPLE_8_BYTES_HEX = (
+ 0x1005,
+ 0x1007,
+ 0x1014,
+ 0x1040,
+)
+
+# Multiple type properties that take up 16 bytes.
+MULTIPLE_16_BYTES = (
+ '1048',
+)
+
+MULTIPLE_16_BYTES_HEX = (
+ 0x1048,
+)
+
+
+# Used to format the header for saving only the header.
+HEADER_FORMAT = """From: {From}
+To: {To}
+Cc: {Cc}
+Bcc: {Bcc}
+Subject: {subject}
+Date: {Date}
+Message-ID: {Message-Id}
+"""
+
+
+KNOWN_CLASS_TYPES = (
+ 'ipm.activity',
+ 'ipm.appointment', # [MS-OXOCAL]
+ 'ipm.contact', # [MS-OXOCNTC]
+ 'ipm.configuration', # [MS-OXOCFG]
+ 'ipm.distlist',
+ 'ipm.document',
+ 'ipm.ole.class',
+ 'ipm.outlook.recall',
+ 'ipm.note',
+ 'ipm.post',
+ 'ipm.stickynote',
+ 'ipm.recall.report',
+ 'ipm.remote',
+ 'ipm.report',
+ 'ipm.resend',
+ 'ipm.schedule',
+ 'ipm.task',
+ 'ipm.taskrequest',
+ 'report',
+)
+
+# Each item is a tuple of the lowercase class type and the issue number
+# associated with it.
+REFUSED_CLASS_TYPES = (
+ ('ipm.outlook.recall', '235'),
+)
+
+PYTPFLOATINGTIME_START = datetime.datetime(1899, 12, 30)
+NULL_DATE = datetime.datetime(4500, 8, 31, 23, 59)
+
+# Constants used for argparse stuff.
+KNOWN_FILE_FLAGS = (
+ '--out-name',
+)
+NEEDS_ARG = (
+ '--out-name',
+)
+REPOSITORY_URL = 'https://github.com/TeamMsgExtractor/msg-extractor'
+MAINDOC = f"""extract_msg:
+\tExtracts emails and attachments saved in Microsoft Outlook's .msg files.
+
+{REPOSITORY_URL}"""
+
+# Default class ID for the root entry for OleWriter. This should be
+# referencing Outlook if I understand it correctly.
+DEFAULT_CLSID = b'\x0b\r\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00F'
+
+PTYPES = {
+ 0x0000: 'PtypUnspecified',
+ 0x0001: 'PtypNull',
+ 0x0002: 'PtypInteger16', # Signed short.
+ 0x0003: 'PtypInteger32', # Signed int.
+ 0x0004: 'PtypFloating32', # Float.
+ 0x0005: 'PtypFloating64', # Double.
+ 0x0006: 'PtypCurrency',
+ 0x0007: 'PtypFloatingTime',
+ 0x000A: 'PtypErrorCode',
+ 0x000B: 'PtypBoolean',
+ 0x000D: 'PtypObject/PtypEmbeddedTable/Storage',
+ 0x0014: 'PtypInteger64', # Signed longlong.
+ 0x001E: 'PtypString8',
+ 0x001F: 'PtypString',
+ 0x0040: 'PtypTime', # Use filetimeToUtc to convert to unix time stamp.
+ 0x0048: 'PtypGuid',
+ 0x00FB: 'PtypServerId',
+ 0x00FD: 'PtypRestriction',
+ 0x00FE: 'PtypRuleAction',
+ 0x0102: 'PtypBinary',
+ 0x1002: 'PtypMultipleInteger16',
+ 0x1003: 'PtypMultipleInteger32',
+ 0x1004: 'PtypMultipleFloating32',
+ 0x1005: 'PtypMultipleFloating64',
+ 0x1006: 'PtypMultipleCurrency',
+ 0x1007: 'PtypMultipleFloatingTime',
+ 0x1014: 'PtypMultipleInteger64',
+ 0x101E: 'PtypMultipleString8',
+ 0x101F: 'PtypMultipleString',
+ 0x1040: 'PtypMultipleTime',
+ 0x1048: 'PtypMultipleGuid',
+ 0x1102: 'PtypMultipleBinary',
+}
+
+# END CONSTANTS.
\ No newline at end of file
diff --git a/extract_msg/constants/ps.py b/extract_msg/constants/ps.py
new file mode 100644
index 00000000..eb1bf167
--- /dev/null
+++ b/extract_msg/constants/ps.py
@@ -0,0 +1,44 @@
+"""
+Property set identifier constants.
+"""
+
+__all__ = [
+ 'PSETID_ADDRESS',
+ 'PSETID_AIRSYNC',
+ 'PSETID_APPOINTMENT',
+ 'PSETID_ATTACHMENT',
+ 'PSETID_CALENDAR_ASSISTANT',
+ 'PSETID_COMMON',
+ 'PSETID_LOG',
+ 'PSETID_MEETING',
+ 'PSETID_MESSAGING',
+ 'PSETID_NOTE',
+ 'PSETID_POSTRSS',
+ 'PSETID_SHARING',
+ 'PSETID_TASK',
+ 'PSETID_UNIFIEDMESSAGING',
+ 'PSETID_XMLEXTRACTEDENTITIES',
+ 'PS_INTERNET_HEADERS',
+ 'PS_MAPI',
+ 'PS_PUBLIC_STRINGS',
+]
+
+
+PS_MAPI = '{00020328-0000-0000-C000-000000000046}'
+PS_PUBLIC_STRINGS = '{00020329-0000-0000-C000-000000000046}'
+PSETID_COMMON = '{00062008-0000-0000-C000-000000000046}'
+PSETID_ADDRESS = '{00062004-0000-0000-C000-000000000046}'
+PS_INTERNET_HEADERS = '{00020386-0000-0000-C000-000000000046}'
+PSETID_APPOINTMENT = '{00062002-0000-0000-C000-000000000046}'
+PSETID_MEETING = '{6ED8DA90-450B-101B-98DA-00AA003F1305}'
+PSETID_LOG = '{0006200A-0000-0000-C000-000000000046}'
+PSETID_MESSAGING = '{41F28F13-83F4-4114-A584-EEDB5A6B0BFF}'
+PSETID_NOTE = '{0006200E-0000-0000-C000-000000000046}'
+PSETID_POSTRSS = '{00062041-0000-0000-C000-000000000046}'
+PSETID_TASK = '{00062003-0000-0000-C000-000000000046}'
+PSETID_UNIFIEDMESSAGING = '{4442858E-A9E3-4E80-B900-317A210CC15B}'
+PSETID_AIRSYNC = '{71035549-0739-4DCB-9163-00F0580DBBDF}'
+PSETID_SHARING = '{00062040-0000-0000-C000-000000000046}'
+PSETID_XMLEXTRACTEDENTITIES = '{23239608-685D-4732-9C55-4C95CB4E8E33}'
+PSETID_ATTACHMENT = '{96357F7F-59E1-47D0-99A7-46515C183B54}'
+PSETID_CALENDAR_ASSISTANT = '{11000E07-B51B-40D6-AF21-CAA85EDAB1D0}'
\ No newline at end of file
diff --git a/extract_msg/constants/re.py b/extract_msg/constants/re.py
new file mode 100644
index 00000000..0dcd05b9
--- /dev/null
+++ b/extract_msg/constants/re.py
@@ -0,0 +1,33 @@
+"""
+Regular expression constants.
+"""
+
+__all__ = [
+ 'BIN',
+ 'HTML_BODY_START',
+ 'HTML_SAN_SPACE',
+ 'INVALID_FILENAME_CHARACTERS',
+ 'INVALID_OLE_PATH',
+ 'RTF_ENC_BODY_START',
+]
+
+
+import re
+
+
+# Characters that are invalid in a filename.
+INVALID_FILENAME_CHARS = re.compile(r'[\\/:*?"<>|]')
+# Regular expression to find sections of spaces for htmlSanitize.
+HTML_SAN_SPACE = re.compile(' +')
+# Regular expression to find the start of the html body.
+HTML_BODY_START = re.compile(b']*>')
+# Regular expression to find the start of the html body in encapsulated RTF.
+# This is used for one of the pattern types that makes life easy.
+RTF_ENC_BODY_START = re.compile(br'\{\\\*\\htmltag[0-9]* ?]*>\}')
+# This is used in the workaround for decoding issues in RTFDE. We find `\bin`
+# sections and try to remove all of them to help with the decoding.
+BIN = re.compile(br'\\bin([0-9]+) ?')
+# Used in the vaildation of OLE paths. Any of these characters in a name make it
+# invalid.
+INVALID_OLE_PATH = re.compile(r'[:/\\!]')
+
diff --git a/extract_msg/constants/st.py b/extract_msg/constants/st.py
new file mode 100644
index 00000000..7de1534e
--- /dev/null
+++ b/extract_msg/constants/st.py
@@ -0,0 +1,126 @@
+"""
+Struct constants.
+"""
+
+__all__ = [
+ 'ST1'
+ 'ST2',
+ 'ST3',
+ 'STF32',
+ 'STF64',
+ 'STFIX',
+ 'STI16',
+ 'STI32',
+ 'STI64',
+ 'STI8',
+ 'STMF32',
+ 'STMF64',
+ 'STMI16',
+ 'STMI32',
+ 'STMI64',
+ 'STNP_ENT',
+ 'STNP_NAM',
+ 'STPEID',
+ 'STUI32',
+ 'STVAR',
+ 'ST_BC_FIELD_INFO',
+ 'ST_BC_HEAD',
+ 'ST_BE_F32',
+ 'ST_BE_F64',
+ 'ST_BE_I16',
+ 'ST_BE_I32',
+ 'ST_BE_I64',
+ 'ST_BE_I8',
+ 'ST_BE_UI16',
+ 'ST_BE_UI32',
+ 'ST_BE_UI64',
+ 'ST_BE_UI8',
+ 'ST_CF_DIR_ENTRY',
+ 'ST_DATA_UI16',
+ 'ST_DATA_UI32',
+ 'ST_DATA_UI8',
+ 'ST_GUID',
+ 'ST_LE_F32',
+ 'ST_LE_F64',
+ 'ST_LE_I16',
+ 'ST_LE_I32',
+ 'ST_LE_I64',
+ 'ST_LE_I8',
+ 'ST_LE_UI16',
+ 'ST_LE_UI32',
+ 'ST_LE_UI64',
+ 'ST_LE_UI8',
+ 'ST_SYSTEMTIME',
+ 'ST_TZ',
+]
+
+
+import struct
+
+
+# Define pre-compiled structs to make unpacking slightly faster.
+# General structs.
+ST1 = struct.Struct('<8x4I')
+ST2 = struct.Struct('b')
+ST_BE_I16 = struct.Struct('>h')
+ST_BE_I32 = struct.Struct('>i')
+ST_BE_I64 = struct.Struct('>q')
+ST_BE_UI8 = struct.Struct('>B')
+ST_BE_UI16 = struct.Struct('>H')
+ST_BE_UI32 = struct.Struct('>I')
+ST_BE_UI64 = struct.Struct('>Q')
+ST_BE_F32 = struct.Struct('>f')
+ST_BE_F64 = struct.Struct('>d')
\ No newline at end of file
diff --git a/extract_msg/logging-config/logging-nt.json b/extract_msg/data/logging-config/logging-nt.json
similarity index 100%
rename from extract_msg/logging-config/logging-nt.json
rename to extract_msg/data/logging-config/logging-nt.json
diff --git a/extract_msg/logging-config/logging-posix.json b/extract_msg/data/logging-config/logging-posix.json
similarity index 100%
rename from extract_msg/logging-config/logging-posix.json
rename to extract_msg/data/logging-config/logging-posix.json
diff --git a/extract_msg/encoding/__init__.py b/extract_msg/encoding/__init__.py
new file mode 100644
index 00000000..19b04d07
--- /dev/null
+++ b/extract_msg/encoding/__init__.py
@@ -0,0 +1,277 @@
+"""
+File for handling specialized encoding tasks or information.
+"""
+
+__all__ = [
+ 'lookupCodePage',
+]
+
+
+# This adds additional encodings to python.
+import ebcdic as _
+import codecs
+
+from ..exceptions import UnknownCodepageError, UnsupportedEncodingError
+
+
+# This is a dictionary matching the code page number to it's encoding name.
+# The list used to make this can be found here:
+# https://docs.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
+### TODO:
+# Many of these code pages are not supported by Python. As such, we should
+# really implement them ourselves to make sure that if someone wants to use an
+# msg file with one of those encodings, they are able to. Perhaps we should
+# create a seperate module for that?
+# Code pages that currently don't have a supported encoding will be preceded by
+# `# UNSUPPORTED`.
+# For some of these, it is also possible that the name we are trying to find
+# them with is not known to Python. I have already confirmed this for a few of
+# them, and adjusted their names to ones that python would recognize. It is
+# Possible I missed a few.
+_CODE_PAGES = {
+ 37: 'IBM037', # IBM EBCDIC US-Canada
+ 437: 'IBM437', # OEM United States
+ 500: 'IBM500', # IBM EBCDIC International
+ 708: 'ASMO-708', # Arabic (ASMO 708)
+ # UNSUPPORTED.
+ 709: '', # Arabic (ASMO-449+, BCON V4)
+ # UNSUPPORTED.
+ 710: '', # Arabic - Transparent Arabic
+ # UNSUPPORTED.
+ 720: 'DOS-720', # Arabic (Transparent ASMO); Arabic (DOS)
+ 737: 'cp737', # OEM Greek (formerly 437G); Greek (DOS)
+ 775: 'ibm775', # OEM Baltic; Baltic (DOS)
+ 850: 'ibm850', # OEM Multilingual Latin 1; Western European (DOS)
+ 852: 'ibm852', # OEM Latin 2; Central European (DOS)
+ 855: 'IBM855', # OEM Cyrillic (primarily Russian)
+ 857: 'ibm857', # OEM Turkish; Turkish (DOS)
+ 858: 'cp858', # OEM Multilingual Latin 1 + Euro symbol
+ 860: 'IBM860', # OEM Portuguese; Portuguese (DOS)
+ 861: 'ibm861', # OEM Icelandic; Icelandic (DOS)
+ 862: 'cp862', # OEM Hebrew; Hebrew (DOS)
+ 863: 'IBM863', # OEM French Canadian; French Canadian (DOS)
+ 864: 'IBM864', # OEM Arabic; Arabic (864)
+ 865: 'IBM865', # OEM Nordic; Nordic (DOS)
+ 866: 'cp866', # OEM Russian; Cyrillic (DOS)
+ 869: 'ibm869', # OEM Modern Greek; Greek, Modern (DOS)
+ 870: 'cp870', # IBM870 # IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2
+ 874: 'windows-874', # ANSI/OEM Thai (ISO 8859-11); Thai (Windows)
+ 875: 'cp875', # IBM EBCDIC Greek Modern
+ 932: 'shift_jis', # ANSI/OEM Japanese; Japanese (Shift-JIS)
+ 936: 'gb2312', # ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)
+ 949: 'ks_c_5601-1987', # ANSI/OEM Korean (Unified Hangul Code)
+ # We *must* use a custom encoding because the Python implementation differs
+ # from the Microsoft implementation.
+ 950: 'windows-950', # ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5)
+ 1026: 'IBM1026', # IBM EBCDIC Turkish (Latin 5)
+ 1047: 'cp1047', # IBM EBCDIC Latin 1/Open System
+ 1140: 'cp1140', # IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro)
+ 1141: 'cp1141', # IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro)
+ 1142: 'cp1142', # IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro)
+ 1143: 'cp1143', # IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro)
+ 1144: 'cp1144', # IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro)
+ 1145: 'cp1145', # IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro)
+ 1146: 'cp1146', # IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro)
+ 1147: 'cp1147', # IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro)
+ 1148: 'cp1148ms', # IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro)
+ 1149: 'cp1149', # IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro)
+ 1200: 'utf-16-le', # Unicode UTF-16, little endian byte order (BMP of ISO 10646);
+ 1201: 'utf-16-be', # Unicode UTF-16, big endian byte order;
+ 1250: 'windows-1250', # ANSI Central European; Central European (Windows)
+ 1251: 'windows-1251', # ANSI Cyrillic; Cyrillic (Windows)
+ 1252: 'windows-1252', # ANSI Latin 1; Western European (Windows)
+ 1253: 'windows-1253', # ANSI Greek; Greek (Windows)
+ 1254: 'windows-1254', # ANSI Turkish; Turkish (Windows)
+ 1255: 'windows-1255', # ANSI Hebrew; Hebrew (Windows)
+ 1256: 'windows-1256', # ANSI Arabic; Arabic (Windows)
+ 1257: 'windows-1257', # ANSI Baltic; Baltic (Windows)
+ 1258: 'windows-1258', # ANSI/OEM Vietnamese; Vietnamese (Windows)
+ 1361: 'Johab', # Korean (Johab)
+ 10000: 'macintosh', # MAC Roman; Western European (Mac)
+ 10001: 'x-mac-japanese', # Japanese (Mac)
+ # UNSUPPORTED.
+ 10002: 'x-mac-chinesetrad', # MAC Traditional Chinese (Big5); Chinese Traditional (Mac)
+ 10003: 'x-mac-korean', # Korean (Mac)
+ # UNSUPPORTED.
+ 10004: 'x-mac-arabic', # Arabic (Mac)
+ # UNSUPPORTED.
+ 10005: 'x-mac-hebrew', # Hebrew (Mac)
+ 10006: 'x-mac-greek', # Greek (Mac)
+ 10007: 'x-mac-cyrillic', # Cyrillic (Mac)
+ # UNSUPPORTED.
+ 10008: 'x-mac-chinesesimp', # MAC Simplified Chinese (GB 2312); Chinese Simplified (Mac)
+ # UNSUPPORTED.
+ 10010: 'x-mac-romanian', # Romanian (Mac)
+ # UNSUPPORTED.
+ 10017: 'x-mac-ukrainian', # Ukrainian (Mac)
+ # UNSUPPORTED.
+ 10021: 'x-mac-thai', # Thai (Mac)
+ 10029: 'x-mac-ce', # MAC Latin 2; Central European (Mac)
+ 10079: 'x-mac-icelandic', # Icelandic (Mac)
+ 10081: 'x-mac-turkish', # Turkish (Mac)
+ # UNSUPPORTED.
+ 10082: 'x-mac-croatian', # Croatian (Mac)
+ 12000: 'utf-32', # Unicode UTF-32, little endian byte order
+ 12001: 'utf-32BE', # Unicode UTF-32, big endian byte order
+ # UNSUPPORTED.
+ 20000: 'x-Chinese_CNS', # CNS Taiwan; Chinese Traditional (CNS)
+ # UNSUPPORTED.
+ 20001: 'x-cp20001', # TCA Taiwan
+ # UNSUPPORTED.
+ 20002: 'x_Chinese-Eten', # Eten Taiwan; Chinese Traditional (Eten)
+ # UNSUPPORTED.
+ 20003: 'x-cp20003', # IBM5550 Taiwan
+ # UNSUPPORTED.
+ 20004: 'x-cp20004', # TeleText Taiwan
+ # UNSUPPORTED.
+ 20005: 'x-cp20005', # Wang Taiwan
+ # UNSUPPORTED.
+ 20105: 'x-IA5', # IA5 (IRV International Alphabet No. 5, 7-bit); Western European (IA5)
+ # UNSUPPORTED.
+ 20106: 'x-IA5-German', # IA5 German (7-bit)
+ # UNSUPPORTED.
+ 20107: 'x-IA5-Swedish', # IA5 Swedish (7-bit)
+ # UNSUPPORTED.
+ 20108: 'x-IA5-Norwegian', # IA5 Norwegian (7-bit)
+ 20127: 'us-ascii', # US-ASCII (7-bit)
+ # UNSUPPORTED.
+ 20261: 'x-cp20261', # T.61
+ # UNSUPPORTED.
+ 20269: 'x-cp20269', # ISO 6937 Non-Spacing Accent
+ 20273: 'IBM273', # IBM EBCDIC Germany
+ 20277: 'cp277', # IBM EBCDIC Denmark-Norway
+ 20278: 'cp278', # IBM EBCDIC Finland-Sweden
+ 20280: 'cp280', # IBM EBCDIC Italy
+ 20284: 'cp284', # IBM EBCDIC Latin America-Spain
+ 20285: 'cp285', # IBM EBCDIC United Kingdom
+ 20290: 'cp290', # IBM EBCDIC Japanese Katakana Extended
+ 20297: 'cp297', # IBM EBCDIC France
+ 20420: 'cp420', # IBM EBCDIC Arabic
+ # UNSUPPORTED.
+ 20423: 'IBM423', # IBM EBCDIC Greek
+ 20424: 'IBM424', # IBM EBCDIC Hebrew
+ 20833: 'cp833', # IBM EBCDIC Korean Extended
+ 20838: 'cp838', # IBM EBCDIC Thai
+ 20866: 'koi8-r', # Russian (KOI8-R); Cyrillic (KOI8-R)
+ 20871: 'cp871', # IBM EBCDIC Icelandic
+ # UNSUPPORTED.
+ 20880: 'IBM880', # IBM EBCDIC Cyrillic Russian
+ # UNSUPPORTED.
+ 20905: 'IBM905', # IBM EBCDIC Turkish
+ # UNSUPPORTED.
+ 20924: 'IBM00924', # IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)
+ 20932: 'EUC-JP', # Japanese (JIS 0208-1990 and 0212-1990)
+ # UNSUPPORTED.
+ 20936: 'x-cp20936', # Simplified Chinese (GB2312); Chinese Simplified (GB2312-80)
+ # UNSUPPORTED.
+ 20949: 'x-cp20949', # Korean Wansung
+ 21025: 'cp1025', # IBM EBCDIC Cyrillic Serbian-Bulgarian
+ # UNSUPPORTED.
+ 21027: '', # (deprecated)
+ 21866: 'koi8-u', # Ukrainian (KOI8-U); Cyrillic (KOI8-U)
+ 28591: 'iso-8859-1', # ISO 8859-1 Latin 1; Western European (ISO)
+ 28592: 'iso-8859-2', # ISO 8859-2 Central European; Central European (ISO)
+ 28593: 'iso-8859-3', # ISO 8859-3 Latin 3
+ 28594: 'iso-8859-4', # ISO 8859-4 Baltic
+ 28595: 'iso-8859-5', # ISO 8859-5 Cyrillic
+ 28596: 'iso-8859-6', # ISO 8859-6 Arabic
+ 28597: 'iso-8859-7', # ISO 8859-7 Greek
+ 28598: 'iso-8859-8', # ISO 8859-8 Hebrew; Hebrew (ISO-Visual)
+ 28599: 'iso-8859-9', # ISO 8859-9 Turkish
+ 28603: 'iso-8859-13', # ISO 8859-13 Estonian
+ 28605: 'iso-8859-15', # ISO 8859-15 Latin 9
+ # UNSUPPORTED.
+ 29001: 'x-Europa', # Europa 3
+ # UNSUPPORTED.
+ 38598: 'iso-8859-8-i', # ISO 8859-8 Hebrew; Hebrew (ISO-Logical)
+ 50220: 'iso-2022-jp', # ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS)
+ 50221: 'csISO2022JP', # ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana)
+ 50222: 'iso-2022-jp', # ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI)
+ 50225: 'iso-2022-kr', # ISO 2022 Korean
+ # UNSUPPORTED.
+ 50227: 'x-cp50227', # ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022)
+ # UNSUPPORTED.
+ 50229: '', # ISO 2022 Traditional Chinese
+ # UNSUPPORTED.
+ 50930: '', # EBCDIC Japanese (Katakana) Extended
+ # UNSUPPORTED.
+ 50931: '', # EBCDIC US-Canada and Japanese
+ # UNSUPPORTED.
+ 50933: '', # EBCDIC Korean Extended and Korean
+ # UNSUPPORTED.
+ 50935: '', # EBCDIC Simplified Chinese Extended and Simplified Chinese
+ # UNSUPPORTED.
+ 50936: '', # EBCDIC Simplified Chinese
+ # UNSUPPORTED.
+ 50937: '', # EBCDIC US-Canada and Traditional Chinese
+ # UNSUPPORTED.
+ 50939: '', # EBCDIC Japanese (Latin) Extended and Japanese
+ 51932: 'euc-jp', # EUC Japanese
+ 51936: 'EUC-CN', # EUC Simplified Chinese; Chinese Simplified (EUC)
+ 51949: 'euc-kr', # EUC Korean
+ # UNSUPPORTED.
+ 51950: '', # EUC Traditional Chinese
+ 52936: 'hz-gb-2312', # HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)
+ 54936: 'GB18030', # Windows XP and later: GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030)
+ # UNSUPPORTED.
+ 57002: 'x-iscii-de', # ISCII Devanagari
+ # UNSUPPORTED.
+ 57003: 'x-iscii-be', # ISCII Bangla
+ # UNSUPPORTED.
+ 57004: 'x-iscii-ta', # ISCII Tamil
+ # UNSUPPORTED.
+ 57005: 'x-iscii-te', # ISCII Telugu
+ # UNSUPPORTED.
+ 57006: 'x-iscii-as', # ISCII Assamese
+ # UNSUPPORTED.
+ 57007: 'x-iscii-or', # ISCII Odia
+ # UNSUPPORTED.
+ 57008: 'x-iscii-ka', # ISCII Kannada
+ # UNSUPPORTED.
+ 57009: 'x-iscii-ma', # ISCII Malayalam
+ # UNSUPPORTED.
+ 57010: 'x-iscii-gu', # ISCII Gujarati
+ # UNSUPPORTED.
+ 57011: 'x-iscii-pa', # ISCII Punjabi
+ 65000: 'utf-7', # Unicode (UTF-7)
+ 65001: 'utf-8', # Unicode (UTF-8)
+}
+
+# Register new encodings.
+
+def lookupCodePage(id_ : int) -> str:
+ """
+ Converts an encoding id into it's name.
+
+ :raises UnknownCodepageError: The code page was not recognized.
+ :raises UnsupportedEncodingError: The code page was recognized, but no
+ encoding exists in the environment with support for it.
+ """
+ if id_ in _CODE_PAGES:
+ if (page := _CODE_PAGES[id_]):
+ return page
+ else:
+ raise UnsupportedEncodingError(f'Code page {id_} is unsupported.')
+ else:
+ raise UnknownCodepageError(f'Unknown code page {id_}.')
+
+def _lookupEncoding(name):
+ return _codecsInfo.get(name)
+
+from .utils import createSBEncoding as _sb, createVBEncoding as _vb
+from ._dt import (
+ _mac_ce, _mac_cyrillic, _mac_greek, _mac_iceland, _mac_turkish,
+ _win874_dec, _win950_dec
+ )
+
+_codecsInfo = {
+ 'x_mac_ce': _sb('x-mac-ce', _mac_ce.decodingTable),
+ 'x_mac_cyrillic': _sb('x-mac-cyrillic', _mac_cyrillic.decodingTable),
+ 'x_mac_greek': _sb('x-mac-greek', _mac_greek.decodingTable),
+ 'x_mac_icelandic': _sb('x-mac-icelandic', _mac_iceland.decodingTable),
+ 'x_mac_turkish': _sb('x-mac-turkish', _mac_turkish.decodingTable),
+ 'windows_950': _vb('windows-950', _win950_dec.decodingTable),
+ 'windows_874': _sb('windows-874', _win874_dec.decodingTable),
+}
+
+codecs.register(_lookupEncoding)
\ No newline at end of file
diff --git a/extract_msg/encoding/_dt/__init__.py b/extract_msg/encoding/_dt/__init__.py
new file mode 100644
index 00000000..f23634ca
--- /dev/null
+++ b/extract_msg/encoding/_dt/__init__.py
@@ -0,0 +1,4 @@
+"""
+Decoding tables for the various encodings implemented. This allows the encoding
+definitions to be neater.
+"""
\ No newline at end of file
diff --git a/extract_msg/encoding/_dt/_mac_ce.py b/extract_msg/encoding/_dt/_mac_ce.py
new file mode 100644
index 00000000..a23e582c
--- /dev/null
+++ b/extract_msg/encoding/_dt/_mac_ce.py
@@ -0,0 +1,8 @@
+# Based on https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/MAC/LATIN2.TXT
+
+__all__ = [
+ 'decodingTable',
+]
+
+
+decodingTable={0:'\x00',1:'\x01',2:'\x02',3:'\x03',4:'\x04',5:'\x05',6:'\x06',7:'\x07',8:'\x08',9:'\t',10:'\n',11:'\x0b',12:'\x0c',13:'\r',14:'\x0e',15:'\x0f',16:'\x10',17:'\x11',18:'\x12',19:'\x13',20:'\x14',21:'\x15',22:'\x16',23:'\x17',24:'\x18',25:'\x19',26:'\x1a',27:'\x1b',28:'\x1c',29:'\x1d',30:'\x1e',31:'\x1f',32:' ',33:'!',34:'"',35:'#',36:'$',37:'%',38:'&',39:"'",40:'(',41:')',42:'*',43:'+',44:',',45:'-',46:'.',47:'/',48:'0',49:'1',50:'2',51:'3',52:'4',53:'5',54:'6',55:'7',56:'8',57:'9',58:':',59:';',60:'<',61:'=',62:'>',63:'?',64:'@',65:'A',66:'B',67:'C',68:'D',69:'E',70:'F',71:'G',72:'H',73:'I',74:'J',75:'K',76:'L',77:'M',78:'N',79:'O',80:'P',81:'Q',82:'R',83:'S',84:'T',85:'U',86:'V',87:'W',88:'X',89:'Y',90:'Z',91:'[',92:'\\',93:']',94:'^',95:'_',96:'`',97:'a',98:'b',99:'c',100:'d',101:'e',102:'f',103:'g',104:'h',105:'i',106:'j',107:'k',108:'l',109:'m',110:'n',111:'o',112:'p',113:'q',114:'r',115:'s',116:'t',117:'u',118:'v',119:'w',120:'x',121:'y',122:'z',123:'{',124:'|',125:'}',126:'~',127:'\x7f',128:'\xC4',129:'\u0100',130:'\u0101',131:'\xC9',132:'\u0104',133:'\xD6',134:'\xDC',135:'\xE1',136:'\u0105',137:'\u010C',138:'\xE4',139:'\u010D',140:'\u0106',141:'\u0107',142:'\xE9',143:'\u0179',144:'\u017A',145:'\u010E',146:'\xED',147:'\u010F',148:'\u0112',149:'\u0113',150:'\u0116',151:'\xF3',152:'\u0117',153:'\xF4',154:'\xF6',155:'\xF5',156:'\xFA',157:'\u011A',158:'\u011B',159:'\xFC',160:'\u2020',161:'\xB0',162:'\u0118',163:'\xA3',164:'\xA7',165:'\u2022',166:'\xB6',167:'\xDF',168:'\xAE',169:'\xA9',170:'\u2122',171:'\u0119',172:'\xA8',173:'\u2260',174:'\u0123',175:'\u012E',176:'\u012F',177:'\u012A',178:'\u2264',179:'\u2265',180:'\u012B',181:'\u0136',182:'\u2202',183:'\u2211',184:'\u0142',185:'\u013B',186:'\u013C',187:'\u013D',188:'\u013E',189:'\u0139',190:'\u013A',191:'\u0145',192:'\u0146',193:'\u0143',194:'\xAC',195:'\u221A',196:'\u0144',197:'\u0147',198:'\u2206',199:'\xAB',200:'\xBB',201:'\u2026',202:'\xA0',203:'\u0148',204:'\u0150',205:'\xD5',206:'\u0151',207:'\u014C',208:'\u2013',209:'\u2014',210:'\u201C',211:'\u201D',212:'\u2018',213:'\u2019',214:'\xF7',215:'\u25CA',216:'\u014D',217:'\u0154',218:'\u0155',219:'\u0158',220:'\u2039',221:'\u203A',222:'\u0159',223:'\u0156',224:'\u0157',225:'\u0160',226:'\u201A',227:'\u201E',228:'\u0161',229:'\u015A',230:'\u015B',231:'\xC1',232:'\u0164',233:'\u0165',234:'\xCD',235:'\u017D',236:'\u017E',237:'\u016A',238:'\xD3',239:'\xD4',240:'\u016B',241:'\u016E',242:'\xDA',243:'\u016F',244:'\u0170',245:'\u0171',246:'\u0172',247:'\u0173',248:'\xDD',249:'\xFD',250:'\u0137',251:'\u017B',252:'\u0141',253:'\u017C',254:'\u0122',255:'\u02C7'}
\ No newline at end of file
diff --git a/extract_msg/encoding/_dt/_mac_cyrillic.py b/extract_msg/encoding/_dt/_mac_cyrillic.py
new file mode 100644
index 00000000..125d4e63
--- /dev/null
+++ b/extract_msg/encoding/_dt/_mac_cyrillic.py
@@ -0,0 +1,8 @@
+# Based on https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/MAC/CYRILLIC.TXT
+
+__all__ = [
+ 'decodingTable',
+]
+
+
+decodingTable={0:'\x00',1:'\x01',2:'\x02',3:'\x03',4:'\x04',5:'\x05',6:'\x06',7:'\x07',8:'\x08',9:'\t',10:'\n',11:'\x0b',12:'\x0c',13:'\r',14:'\x0e',15:'\x0f',16:'\x10',17:'\x11',18:'\x12',19:'\x13',20:'\x14',21:'\x15',22:'\x16',23:'\x17',24:'\x18',25:'\x19',26:'\x1a',27:'\x1b',28:'\x1c',29:'\x1d',30:'\x1e',31:'\x1f',32:' ',33:'!',34:'"',35:'#',36:'$',37:'%',38:'&',39:"'",40:'(',41:')',42:'*',43:'+',44:',',45:'-',46:'.',47:'/',48:'0',49:'1',50:'2',51:'3',52:'4',53:'5',54:'6',55:'7',56:'8',57:'9',58:':',59:';',60:'<',61:'=',62:'>',63:'?',64:'@',65:'A',66:'B',67:'C',68:'D',69:'E',70:'F',71:'G',72:'H',73:'I',74:'J',75:'K',76:'L',77:'M',78:'N',79:'O',80:'P',81:'Q',82:'R',83:'S',84:'T',85:'U',86:'V',87:'W',88:'X',89:'Y',90:'Z',91:'[',92:'\\',93:']',94:'^',95:'_',96:'`',97:'a',98:'b',99:'c',100:'d',101:'e',102:'f',103:'g',104:'h',105:'i',106:'j',107:'k',108:'l',109:'m',110:'n',111:'o',112:'p',113:'q',114:'r',115:'s',116:'t',117:'u',118:'v',119:'w',120:'x',121:'y',122:'z',123:'{',124:'|',125:'}',126:'~',127:'\x7f',128:'\u0410',129:'\u0411',130:'\u0412',131:'\u0413',132:'\u0414',133:'\u0415',134:'\u0416',135:'\u0417',136:'\u0418',137:'\u0419',138:'\u041A',139:'\u041B',140:'\u041C',141:'\u041D',142:'\u041E',143:'\u041F',144:'\u0420',145:'\u0421',146:'\u0422',147:'\u0423',148:'\u0424',149:'\u0425',150:'\u0426',151:'\u0427',152:'\u0428',153:'\u0429',154:'\u042A',155:'\u042B',156:'\u042C',157:'\u042D',158:'\u042E',159:'\u042F',160:'\u2020',161:'\xB0',162:'\xA2',163:'\xA3',164:'\xA7',165:'\u2022',166:'\xB6',167:'\u0406',168:'\xAE',169:'\xA9',170:'\u2122',171:'\u0402',172:'\u0452',173:'\u2260',174:'\u0403',175:'\u0453',176:'\u221E',177:'\xB1',178:'\u2264',179:'\u2265',180:'\u0456',181:'\xB5',182:'\u2202',183:'\u0408',184:'\u0404',185:'\u0454',186:'\u0407',187:'\u0457',188:'\u0409',189:'\u0459',190:'\u040A',191:'\u045A',192:'\u0458',193:'\u0405',194:'\xAC',195:'\u221A',196:'\u0192',197:'\u2248',198:'\u2206',199:'\xAB',200:'\xBB',201:'\u2026',202:'\xA0',203:'\u040B',204:'\u045B',205:'\u040C',206:'\u045C',207:'\u0455',208:'\u2013',209:'\u2014',210:'\u201C',211:'\u201D',212:'\u2018',213:'\u2019',214:'\xF7',215:'\u201E',216:'\u040E',217:'\u045E',218:'\u040F',219:'\u045F',220:'\u2116',221:'\u0401',222:'\u0451',223:'\u044F',224:'\u0430',225:'\u0431',226:'\u0432',227:'\u0433',228:'\u0434',229:'\u0435',230:'\u0436',231:'\u0437',232:'\u0438',233:'\u0439',234:'\u043A',235:'\u043B',236:'\u043C',237:'\u043D',238:'\u043E',239:'\u043F',240:'\u0440',241:'\u0441',242:'\u0442',243:'\u0443',244:'\u0444',245:'\u0445',246:'\u0446',247:'\u0447',248:'\u0448',249:'\u0449',250:'\u044A',251:'\u044B',252:'\u044C',253:'\u044D',254:'\u044E',255:'\xA4'}
\ No newline at end of file
diff --git a/extract_msg/encoding/_dt/_mac_greek.py b/extract_msg/encoding/_dt/_mac_greek.py
new file mode 100644
index 00000000..41985a13
--- /dev/null
+++ b/extract_msg/encoding/_dt/_mac_greek.py
@@ -0,0 +1,8 @@
+# Based on https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/MAC/GREEK.TXT
+
+__all__ = [
+ 'decodingTable',
+]
+
+
+decodingTable={0:'\x00',1:'\x01',2:'\x02',3:'\x03',4:'\x04',5:'\x05',6:'\x06',7:'\x07',8:'\x08',9:'\t',10:'\n',11:'\x0b',12:'\x0c',13:'\r',14:'\x0e',15:'\x0f',16:'\x10',17:'\x11',18:'\x12',19:'\x13',20:'\x14',21:'\x15',22:'\x16',23:'\x17',24:'\x18',25:'\x19',26:'\x1a',27:'\x1b',28:'\x1c',29:'\x1d',30:'\x1e',31:'\x1f',32:' ',33:'!',34:'"',35:'#',36:'$',37:'%',38:'&',39:"'",40:'(',41:')',42:'*',43:'+',44:',',45:'-',46:'.',47:'/',48:'0',49:'1',50:'2',51:'3',52:'4',53:'5',54:'6',55:'7',56:'8',57:'9',58:':',59:';',60:'<',61:'=',62:'>',63:'?',64:'@',65:'A',66:'B',67:'C',68:'D',69:'E',70:'F',71:'G',72:'H',73:'I',74:'J',75:'K',76:'L',77:'M',78:'N',79:'O',80:'P',81:'Q',82:'R',83:'S',84:'T',85:'U',86:'V',87:'W',88:'X',89:'Y',90:'Z',91:'[',92:'\\',93:']',94:'^',95:'_',96:'`',97:'a',98:'b',99:'c',100:'d',101:'e',102:'f',103:'g',104:'h',105:'i',106:'j',107:'k',108:'l',109:'m',110:'n',111:'o',112:'p',113:'q',114:'r',115:'s',116:'t',117:'u',118:'v',119:'w',120:'x',121:'y',122:'z',123:'{',124:'|',125:'}',126:'~',127:'\x7f',128:'\xC4',129:'\xB9',130:'\xB2',131:'\xC9',132:'\xB3',133:'\xD6',134:'\xDC',135:'\u0385',136:'\xE0',137:'\xE2',138:'\xE4',139:'\u0384',140:'\xA8',141:'\xE7',142:'\xE9',143:'\xE8',144:'\xEA',145:'\xEB',146:'\xA3',147:'\u2122',148:'\xEE',149:'\xEF',150:'\u2022',151:'\xBD',152:'\u2030',153:'\xF4',154:'\xF6',155:'\xA6',156:'\xAD',157:'\xF9',158:'\xFB',159:'\xFC',160:'\u2020',161:'\u0393',162:'\u0394',163:'\u0398',164:'\u039B',165:'\u039E',166:'\u03A0',167:'\xDF',168:'\xAE',169:'\xA9',170:'\u03A3',171:'\u03AA',172:'\xA7',173:'\u2260',174:'\xB0',175:'\u0387',176:'\u0391',177:'\xB1',178:'\u2264',179:'\u2265',180:'\xA5',181:'\u0392',182:'\u0395',183:'\u0396',184:'\u0397',185:'\u0399',186:'\u039A',187:'\u039C',188:'\u03A6',189:'\u03AB',190:'\u03A8',191:'\u03A9',192:'\u03AC',193:'\u039D',194:'\xAC',195:'\u039F',196:'\u03A1',197:'\u2248',198:'\u03A4',199:'\xAB',200:'\xBB',201:'\u2026',202:'\xA0',203:'\u03A5',204:'\u03A7',205:'\u0386',206:'\u0388',207:'\u0153',208:'\u2013',209:'\u2015',210:'\u201C',211:'\u201D',212:'\u2018',213:'\u2019',214:'\xF7',215:'\u0389',216:'\u038A',217:'\u038C',218:'\u038E',219:'\u03AD',220:'\u03AE',221:'\u03AF',222:'\u03CC',223:'\u038F',224:'\u03CD',225:'\u03B1',226:'\u03B2',227:'\u03C8',228:'\u03B4',229:'\u03B5',230:'\u03C6',231:'\u03B3',232:'\u03B7',233:'\u03B9',234:'\u03BE',235:'\u03BA',236:'\u03BB',237:'\u03BC',238:'\u03BD',239:'\u03BF',240:'\u03C0',241:'\u03CE',242:'\u03C1',243:'\u03C3',244:'\u03C4',245:'\u03B8',246:'\u03C9',247:'\u03C2',248:'\u03C7',249:'\u03C5',250:'\u03B6',251:'\u03CA',252:'\u03CB',253:'\u0390',254:'\u03B0'}
\ No newline at end of file
diff --git a/extract_msg/encoding/_dt/_mac_iceland.py b/extract_msg/encoding/_dt/_mac_iceland.py
new file mode 100644
index 00000000..2fb210bd
--- /dev/null
+++ b/extract_msg/encoding/_dt/_mac_iceland.py
@@ -0,0 +1,8 @@
+# Based on https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/MAC/ICELAND.TXT
+
+__all__ = [
+ 'decodingTable',
+]
+
+
+decodingTable={0:'\x00',1:'\x01',2:'\x02',3:'\x03',4:'\x04',5:'\x05',6:'\x06',7:'\x07',8:'\x08',9:'\t',10:'\n',11:'\x0b',12:'\x0c',13:'\r',14:'\x0e',15:'\x0f',16:'\x10',17:'\x11',18:'\x12',19:'\x13',20:'\x14',21:'\x15',22:'\x16',23:'\x17',24:'\x18',25:'\x19',26:'\x1a',27:'\x1b',28:'\x1c',29:'\x1d',30:'\x1e',31:'\x1f',32:' ',33:'!',34:'"',35:'#',36:'$',37:'%',38:'&',39:"'",40:'(',41:')',42:'*',43:'+',44:',',45:'-',46:'.',47:'/',48:'0',49:'1',50:'2',51:'3',52:'4',53:'5',54:'6',55:'7',56:'8',57:'9',58:':',59:';',60:'<',61:'=',62:'>',63:'?',64:'@',65:'A',66:'B',67:'C',68:'D',69:'E',70:'F',71:'G',72:'H',73:'I',74:'J',75:'K',76:'L',77:'M',78:'N',79:'O',80:'P',81:'Q',82:'R',83:'S',84:'T',85:'U',86:'V',87:'W',88:'X',89:'Y',90:'Z',91:'[',92:'\\',93:']',94:'^',95:'_',96:'`',97:'a',98:'b',99:'c',100:'d',101:'e',102:'f',103:'g',104:'h',105:'i',106:'j',107:'k',108:'l',109:'m',110:'n',111:'o',112:'p',113:'q',114:'r',115:'s',116:'t',117:'u',118:'v',119:'w',120:'x',121:'y',122:'z',123:'{',124:'|',125:'}',126:'~',127:'\x7f',128:'\xC4',129:'\xC5',130:'\xC7',131:'\xC9',132:'\xD1',133:'\xD6',134:'\xDC',135:'\xE1',136:'\xE0',137:'\xE2',138:'\xE4',139:'\xE3',140:'\xE5',141:'\xE7',142:'\xE9',143:'\xE8',144:'\xEA',145:'\xEB',146:'\xED',147:'\xEC',148:'\xEE',149:'\xEF',150:'\xF1',151:'\xF3',152:'\xF2',153:'\xF4',154:'\xF6',155:'\xF5',156:'\xFA',157:'\xF9',158:'\xFB',159:'\xFC',160:'\xDD',161:'\xB0',162:'\xA2',163:'\xA3',164:'\xA7',165:'\u2022',166:'\xB6',167:'\xDF',168:'\xAE',169:'\xA9',170:'\u2122',171:'\xB4',172:'\xA8',173:'\u2260',174:'\xC6',175:'\xD8',176:'\u221E',177:'\xB1',178:'\u2264',179:'\u2265',180:'\xA5',181:'\xB5',182:'\u2202',183:'\u2211',184:'\u220F',185:'\u03C0',186:'\u222B',187:'\xAA',188:'\xBA',189:'\u2126',190:'\xE6',191:'\xF8',192:'\xBF',193:'\xA1',194:'\xAC',195:'\u221A',196:'\u0192',197:'\u2248',198:'\u2206',199:'\xAB',200:'\xBB',201:'\u2026',202:'\xA0',203:'\xC0',204:'\xC3',205:'\xD5',206:'\u0152',207:'\u0153',208:'\u2013',209:'\u2014',210:'\u201C',211:'\u201D',212:'\u2018',213:'\u2019',214:'\xF7',215:'\u25CA',216:'\xFF',217:'\u0178',218:'\u2044',219:'\xA4',220:'\xD0',221:'\xF0',222:'\xDE',223:'\xFE',224:'\xFD',225:'\xB7',226:'\u201A',227:'\u201E',228:'\u2030',229:'\xC2',230:'\xCA',231:'\xC1',232:'\xCB',233:'\xC8',234:'\xCD',235:'\xCE',236:'\xCF',237:'\xCC',238:'\xD3',239:'\xD4',241:'\xD2',242:'\xDA',243:'\xDB',244:'\xD9',245:'\u0131',246:'\u02C6',247:'\u02DC',248:'\xAF',249:'\u02D8',250:'\u02D9',251:'\u02DA',252:'\xB8',253:'\u02DD',254:'\u02DB',255:'\u02C7'}
\ No newline at end of file
diff --git a/extract_msg/encoding/_dt/_mac_turkish.py b/extract_msg/encoding/_dt/_mac_turkish.py
new file mode 100644
index 00000000..67678731
--- /dev/null
+++ b/extract_msg/encoding/_dt/_mac_turkish.py
@@ -0,0 +1,8 @@
+# Based on https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/MAC/TURKISH.TXT
+
+__all__ = [
+ 'decodingTable',
+]
+
+
+decodingTable={0:'\x00',1:'\x01',2:'\x02',3:'\x03',4:'\x04',5:'\x05',6:'\x06',7:'\x07',8:'\x08',9:'\t',10:'\n',11:'\x0b',12:'\x0c',13:'\r',14:'\x0e',15:'\x0f',16:'\x10',17:'\x11',18:'\x12',19:'\x13',20:'\x14',21:'\x15',22:'\x16',23:'\x17',24:'\x18',25:'\x19',26:'\x1a',27:'\x1b',28:'\x1c',29:'\x1d',30:'\x1e',31:'\x1f',32:' ',33:'!',34:'"',35:'#',36:'$',37:'%',38:'&',39:"'",40:'(',41:')',42:'*',43:'+',44:',',45:'-',46:'.',47:'/',48:'0',49:'1',50:'2',51:'3',52:'4',53:'5',54:'6',55:'7',56:'8',57:'9',58:':',59:';',60:'<',61:'=',62:'>',63:'?',64:'@',65:'A',66:'B',67:'C',68:'D',69:'E',70:'F',71:'G',72:'H',73:'I',74:'J',75:'K',76:'L',77:'M',78:'N',79:'O',80:'P',81:'Q',82:'R',83:'S',84:'T',85:'U',86:'V',87:'W',88:'X',89:'Y',90:'Z',91:'[',92:'\\',93:']',94:'^',95:'_',96:'`',97:'a',98:'b',99:'c',100:'d',101:'e',102:'f',103:'g',104:'h',105:'i',106:'j',107:'k',108:'l',109:'m',110:'n',111:'o',112:'p',113:'q',114:'r',115:'s',116:'t',117:'u',118:'v',119:'w',120:'x',121:'y',122:'z',123:'{',124:'|',125:'}',126:'~',127:'\x7f',128:'\xC4',129:'\xC5',130:'\xC7',131:'\xC9',132:'\xD1',133:'\xD6',134:'\xDC',135:'\xE1',136:'\xE0',137:'\xE2',138:'\xE4',139:'\xE3',140:'\xE5',141:'\xE7',142:'\xE9',143:'\xE8',144:'\xEA',145:'\xEB',146:'\xED',147:'\xEC',148:'\xEE',149:'\xEF',150:'\xF1',151:'\xF3',152:'\xF2',153:'\xF4',154:'\xF6',155:'\xF5',156:'\xFA',157:'\xF9',158:'\xFB',159:'\xFC',160:'\u2020',161:'\xB0',162:'\xA2',163:'\xA3',164:'\xA7',165:'\u2022',166:'\xB6',167:'\xDF',168:'\xAE',169:'\xA9',170:'\u2122',171:'\xB4',172:'\xA8',173:'\u2260',174:'\xC6',175:'\xD8',176:'\u221E',177:'\xB1',178:'\u2264',179:'\u2265',180:'\xA5',181:'\xB5',182:'\u2202',183:'\u2211',184:'\u220F',185:'\u03C0',186:'\u222B',187:'\xAA',188:'\xBA',189:'\u2126',190:'\xE6',191:'\xF8',192:'\xBF',193:'\xA1',194:'\xAC',195:'\u221A',196:'\u0192',197:'\u2248',198:'\u2206',199:'\xAB',200:'\xBB',201:'\u2026',202:'\xA0',203:'\xC0',204:'\xC3',205:'\xD5',206:'\u0152',207:'\u0153',208:'\u2013',209:'\u2014',210:'\u201C',211:'\u201D',212:'\u2018',213:'\u2019',214:'\xF7',215:'\u25CA',216:'\xFF',217:'\u0178',218:'\u011E',219:'\u011F',220:'\u0130',221:'\u0131',222:'\u015E',223:'\u015F',224:'\u2021',225:'\xB7',226:'\u201A',227:'\u201E',228:'\u2030',229:'\xC2',230:'\xCA',231:'\xC1',232:'\xCB',233:'\xC8',234:'\xCD',235:'\xCE',236:'\xCF',237:'\xCC',238:'\xD3',239:'\xD4',241:'\xD2',242:'\xDA',243:'\xDB',244:'\xD9',246:'\u02C6',247:'\u02DC',248:'\xAF',249:'\u02D8',250:'\u02D9',251:'\u02DA',252:'\xB8',253:'\u02DD',254:'\u02DB',255:'\u02C7'}
\ No newline at end of file
diff --git a/extract_msg/encoding/_dt/_win874_dec.py b/extract_msg/encoding/_dt/_win874_dec.py
new file mode 100644
index 00000000..406f833f
--- /dev/null
+++ b/extract_msg/encoding/_dt/_win874_dec.py
@@ -0,0 +1,8 @@
+# Based on https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP874.TXT
+
+__all__ = [
+ 'decodingTable',
+]
+
+
+decodingTable={0:'\x00',1:'\x01',2:'\x02',3:'\x03',4:'\x04',5:'\x05',6:'\x06',7:'\x07',8:'\x08',9:'\t',10:'\n',11:'\x0B',12:'\x0C',13:'\r',14:'\x0E',15:'\x0F',16:'\x10',17:'\x11',18:'\x12',19:'\x13',20:'\x14',21:'\x15',22:'\x16',23:'\x17',24:'\x18',25:'\x19',26:'\x1A',27:'\x1B',28:'\x1C',29:'\x1D',30:'\x1E',31:'\x1F',32:' ',33:'!',34:'"',35:'#',36:'$',37:'%',38:'&',39:"'",40:'(',41:')',42:'*',43:'+',44:',',45:'-',46:'.',47:'/',48:'0',49:'1',50:'2',51:'3',52:'4',53:'5',54:'6',55:'7',56:'8',57:'9',58:':',59:';',60:'<',61:'=',62:'>',63:'?',64:'@',65:'A',66:'B',67:'C',68:'D',69:'E',70:'F',71:'G',72:'H',73:'I',74:'J',75:'K',76:'L',77:'M',78:'N',79:'O',80:'P',81:'Q',82:'R',83:'S',84:'T',85:'U',86:'V',87:'W',88:'X',89:'Y',90:'Z',91:'[',92:'\\',93:']',94:'^',95:'_',96:'`',97:'a',98:'b',99:'c',100:'d',101:'e',102:'f',103:'g',104:'h',105:'i',106:'j',107:'k',108:'l',109:'m',110:'n',111:'o',112:'p',113:'q',114:'r',115:'s',116:'t',117:'u',118:'v',119:'w',120:'x',121:'y',122:'z',123:'{',124:'|',125:'}',126:'~',127:'\x7F',128:'\x80',129:'\u20AC',133:'\u2026',145:'\u2018',146:'\u2019',147:'\u201C',148:'\u201D',149:'\u2022',150:'\u2013',151:'\u2014',160:'\xA0',161:'\u0E01',162:'\u0E02',163:'\u0E03',164:'\u0E04',165:'\u0E05',166:'\u0E06',167:'\u0E07',168:'\u0E08',169:'\u0E09',170:'\u0E0A',171:'\u0E0B',172:'\u0E0C',173:'\u0E0D',174:'\u0E0E',175:'\u0E0F',176:'\u0E10',177:'\u0E11',178:'\u0E12',179:'\u0E13',180:'\u0E14',181:'\u0E15',182:'\u0E16',183:'\u0E17',184:'\u0E18',185:'\u0E19',186:'\u0E1A',187:'\u0E1B',188:'\u0E1C',189:'\u0E1D',190:'\u0E1E',191:'\u0E1F',192:'\u0E20',193:'\u0E21',194:'\u0E22',195:'\u0E23',196:'\u0E24',197:'\u0E25',198:'\u0E26',199:'\u0E27',200:'\u0E28',201:'\u0E29',202:'\u0E2A',203:'\u0E2B',204:'\u0E2C',205:'\u0E2D',206:'\u0E2E',207:'\u0E2F',208:'\u0E30',209:'\u0E31',210:'\u0E32',211:'\u0E33',212:'\u0E34',213:'\u0E35',214:'\u0E36',215:'\u0E37',216:'\u0E38',217:'\u0E39',218:'\u0E3A',224:'\u0E3F',225:'\u0E40',226:'\u0E41',227:'\u0E42',228:'\u0E43',229:'\u0E44',230:'\u0E45',231:'\u0E46',232:'\u0E47',233:'\u0E48',234:'\u0E49',235:'\u0E4A',236:'\u0E4B',237:'\u0E4C',238:'\u0E4D',239:'\u0E4E',240:'\u0E4F',241:'\u0E50',242:'\u0E51',243:'\u0E52',244:'\u0E53',245:'\u0E54',246:'\u0E55',247:'\u0E56',248:'\u0E57',249:'\u0E58',250:'\u0E59',251:'\u0E5A',252:'\u0E5B'}
\ No newline at end of file
diff --git a/extract_msg/encoding/_dt/_win950_dec.py b/extract_msg/encoding/_dt/_win950_dec.py
new file mode 100644
index 00000000..49785b22
--- /dev/null
+++ b/extract_msg/encoding/_dt/_win950_dec.py
@@ -0,0 +1,6 @@
+__all__ = [
+ 'decodingTable'
+]
+
+
+decodingTable={0:'\x00',1:'\x01',2:'\x02',3:'\x03',4:'\x04',5:'\x05',6:'\x06',7:'\x07',8:'\x08',9:'\t',10:'\n',11:'\x0B',12:'\x0C',13:'\r',14:'\x0E',15:'\x0F',16:'\x10',17:'\x11',18:'\x12',19:'\x13',20:'\x14',21:'\x15',22:'\x16',23:'\x17',24:'\x18',25:'\x19',26:'\x1A',27:'\x1B',28:'\x1C',29:'\x1D',30:'\x1E',31:'\x1F',32:' ',33:'!',34:'"',35:'#',36:'$',37:'%',38:'&',39:"'",40:'(',41:')',42:'*',43:'+',44:',',45:'-',46:'.',47:'/',48:'0',49:'1',50:'2',51:'3',52:'4',53:'5',54:'6',55:'7',56:'8',57:'9',58:':',59:';',60:'<',61:'=',62:'>',63:'?',64:'@',65:'A',66:'B',67:'C',68:'D',69:'E',70:'F',71:'G',72:'H',73:'I',74:'J',75:'K',76:'L',77:'M',78:'N',79:'O',80:'P',81:'Q',82:'R',83:'S',84:'T',85:'U',86:'V',87:'W',88:'X',89:'Y',90:'Z',91:'[',92:'\\',93:']',94:'^',95:'_',96:'`',97:'a',98:'b',99:'c',100:'d',101:'e',102:'f',103:'g',104:'h',105:'i',106:'j',107:'k',108:'l',109:'m',110:'n',111:'o',112:'p',113:'q',114:'r',115:'s',116:'t',117:'u',118:'v',119:'w',120:'x',121:'y',122:'z',123:'{',124:'|',125:'}',126:'~',127:'\x7F',128:'\x80',255:'\uF8F8',33088:'\uEEB8',33089:'\uEEB9',33090:'\uEEBA',33091:'\uEEBB',33092:'\uEEBC',33093:'\uEEBD',33094:'\uEEBE',33095:'\uEEBF',33096:'\uEEC0',33097:'\uEEC1',33098:'\uEEC2',33099:'\uEEC3',33100:'\uEEC4',33101:'\uEEC5',33102:'\uEEC6',33103:'\uEEC7',33104:'\uEEC8',33105:'\uEEC9',33106:'\uEECA',33107:'\uEECB',33108:'\uEECC',33109:'\uEECD',33110:'\uEECE',33111:'\uEECF',33112:'\uEED0',33113:'\uEED1',33114:'\uEED2',33115:'\uEED3',33116:'\uEED4',33117:'\uEED5',33118:'\uEED6',33119:'\uEED7',33120:'\uEED8',33121:'\uEED9',33122:'\uEEDA',33123:'\uEEDB',33124:'\uEEDC',33125:'\uEEDD',33126:'\uEEDE',33127:'\uEEDF',33128:'\uEEE0',33129:'\uEEE1',33130:'\uEEE2',33131:'\uEEE3',33132:'\uEEE4',33133:'\uEEE5',33134:'\uEEE6',33135:'\uEEE7',33136:'\uEEE8',33137:'\uEEE9',33138:'\uEEEA',33139:'\uEEEB',33140:'\uEEEC',33141:'\uEEED',33142:'\uEEEE',33143:'\uEEEF',33144:'\uEEF0',33145:'\uEEF1',33146:'\uEEF2',33147:'\uEEF3',33148:'\uEEF4',33149:'\uEEF5',33150:'\uEEF6',33185:'\uEEF7',33186:'\uEEF8',33187:'\uEEF9',33188:'\uEEFA',33189:'\uEEFB',33190:'\uEEFC',33191:'\uEEFD',33192:'\uEEFE',33193:'\uEEFF',33194:'\uEF00',33195:'\uEF01',33196:'\uEF02',33197:'\uEF03',33198:'\uEF04',33199:'\uEF05',33200:'\uEF06',33201:'\uEF07',33202:'\uEF08',33203:'\uEF09',33204:'\uEF0A',33205:'\uEF0B',33206:'\uEF0C',33207:'\uEF0D',33208:'\uEF0E',33209:'\uEF0F',33210:'\uEF10',33211:'\uEF11',33212:'\uEF12',33213:'\uEF13',33214:'\uEF14',33215:'\uEF15',33216:'\uEF16',33217:'\uEF17',33218:'\uEF18',33219:'\uEF19',33220:'\uEF1A',33221:'\uEF1B',33222:'\uEF1C',33223:'\uEF1D',33224:'\uEF1E',33225:'\uEF1F',33226:'\uEF20',33227:'\uEF21',33228:'\uEF22',33229:'\uEF23',33230:'\uEF24',33231:'\uEF25',33232:'\uEF26',33233:'\uEF27',33234:'\uEF28',33235:'\uEF29',33236:'\uEF2A',33237:'\uEF2B',33238:'\uEF2C',33239:'\uEF2D',33240:'\uEF2E',33241:'\uEF2F',33242:'\uEF30',33243:'\uEF31',33244:'\uEF32',33245:'\uEF33',33246:'\uEF34',33247:'\uEF35',33248:'\uEF36',33249:'\uEF37',33250:'\uEF38',33251:'\uEF39',33252:'\uEF3A',33253:'\uEF3B',33254:'\uEF3C',33255:'\uEF3D',33256:'\uEF3E',33257:'\uEF3F',33258:'\uEF40',33259:'\uEF41',33260:'\uEF42',33261:'\uEF43',33262:'\uEF44',33263:'\uEF45',33264:'\uEF46',33265:'\uEF47',33266:'\uEF48',33267:'\uEF49',33268:'\uEF4A',33269:'\uEF4B',33270:'\uEF4C',33271:'\uEF4D',33272:'\uEF4E',33273:'\uEF4F',33274:'\uEF50',33275:'\uEF51',33276:'\uEF52',33277:'\uEF53',33278:'\uEF54',33344:'\uEF55',33345:'\uEF56',33346:'\uEF57',33347:'\uEF58',33348:'\uEF59',33349:'\uEF5A',33350:'\uEF5B',33351:'\uEF5C',33352:'\uEF5D',33353:'\uEF5E',33354:'\uEF5F',33355:'\uEF60',33356:'\uEF61',33357:'\uEF62',33358:'\uEF63',33359:'\uEF64',33360:'\uEF65',33361:'\uEF66',33362:'\uEF67',33363:'\uEF68',33364:'\uEF69',33365:'\uEF6A',33366:'\uEF6B',33367:'\uEF6C',33368:'\uEF6D',33369:'\uEF6E',33370:'\uEF6F',33371:'\uEF70',33372:'\uEF71',33373:'\uEF72',33374:'\uEF73',33375:'\uEF74',33376:'\uEF75',33377:'\uEF76',33378:'\uEF77',33379:'\uEF78',33380:'\uEF79',33381:'\uEF7A',33382:'\uEF7B',33383:'\uEF7C',33384:'\uEF7D',33385:'\uEF7E',33386:'\uEF7F',33387:'\uEF80',33388:'\uEF81',33389:'\uEF82',33390:'\uEF83',33391:'\uEF84',33392:'\uEF85',33393:'\uEF86',33394:'\uEF87',33395:'\uEF88',33396:'\uEF89',33397:'\uEF8A',33398:'\uEF8B',33399:'\uEF8C',33400:'\uEF8D',33401:'\uEF8E',33402:'\uEF8F',33403:'\uEF90',33404:'\uEF91',33405:'\uEF92',33406:'\uEF93',33441:'\uEF94',33442:'\uEF95',33443:'\uEF96',33444:'\uEF97',33445:'\uEF98',33446:'\uEF99',33447:'\uEF9A',33448:'\uEF9B',33449:'\uEF9C',33450:'\uEF9D',33451:'\uEF9E',33452:'\uEF9F',33453:'\uEFA0',33454:'\uEFA1',33455:'\uEFA2',33456:'\uEFA3',33457:'\uEFA4',33458:'\uEFA5',33459:'\uEFA6',33460:'\uEFA7',33461:'\uEFA8',33462:'\uEFA9',33463:'\uEFAA',33464:'\uEFAB',33465:'\uEFAC',33466:'\uEFAD',33467:'\uEFAE',33468:'\uEFAF',33469:'\uEFB0',33470:'\uEFB1',33471:'\uEFB2',33472:'\uEFB3',33473:'\uEFB4',33474:'\uEFB5',33475:'\uEFB6',33476:'\uEFB7',33477:'\uEFB8',33478:'\uEFB9',33479:'\uEFBA',33480:'\uEFBB',33481:'\uEFBC',33482:'\uEFBD',33483:'\uEFBE',33484:'\uEFBF',33485:'\uEFC0',33486:'\uEFC1',33487:'\uEFC2',33488:'\uEFC3',33489:'\uEFC4',33490:'\uEFC5',33491:'\uEFC6',33492:'\uEFC7',33493:'\uEFC8',33494:'\uEFC9',33495:'\uEFCA',33496:'\uEFCB',33497:'\uEFCC',33498:'\uEFCD',33499:'\uEFCE',33500:'\uEFCF',33501:'\uEFD0',33502:'\uEFD1',33503:'\uEFD2',33504:'\uEFD3',33505:'\uEFD4',33506:'\uEFD5',33507:'\uEFD6',33508:'\uEFD7',33509:'\uEFD8',33510:'\uEFD9',33511:'\uEFDA',33512:'\uEFDB',33513:'\uEFDC',33514:'\uEFDD',33515:'\uEFDE',33516:'\uEFDF',33517:'\uEFE0',33518:'\uEFE1',33519:'\uEFE2',33520:'\uEFE3',33521:'\uEFE4',33522:'\uEFE5',33523:'\uEFE6',33524:'\uEFE7',33525:'\uEFE8',33526:'\uEFE9',33527:'\uEFEA',33528:'\uEFEB',33529:'\uEFEC',33530:'\uEFED',33531:'\uEFEE',33532:'\uEFEF',33533:'\uEFF0',33534:'\uEFF1',33600:'\uEFF2',33601:'\uEFF3',33602:'\uEFF4',33603:'\uEFF5',33604:'\uEFF6',33605:'\uEFF7',33606:'\uEFF8',33607:'\uEFF9',33608:'\uEFFA',33609:'\uEFFB',33610:'\uEFFC',33611:'\uEFFD',33612:'\uEFFE',33613:'\uEFFF',33614:'\uF000',33615:'\uF001',33616:'\uF002',33617:'\uF003',33618:'\uF004',33619:'\uF005',33620:'\uF006',33621:'\uF007',33622:'\uF008',33623:'\uF009',33624:'\uF00A',33625:'\uF00B',33626:'\uF00C',33627:'\uF00D',33628:'\uF00E',33629:'\uF00F',33630:'\uF010',33631:'\uF011',33632:'\uF012',33633:'\uF013',33634:'\uF014',33635:'\uF015',33636:'\uF016',33637:'\uF017',33638:'\uF018',33639:'\uF019',33640:'\uF01A',33641:'\uF01B',33642:'\uF01C',33643:'\uF01D',33644:'\uF01E',33645:'\uF01F',33646:'\uF020',33647:'\uF021',33648:'\uF022',33649:'\uF023',33650:'\uF024',33651:'\uF025',33652:'\uF026',33653:'\uF027',33654:'\uF028',33655:'\uF029',33656:'\uF02A',33657:'\uF02B',33658:'\uF02C',33659:'\uF02D',33660:'\uF02E',33661:'\uF02F',33662:'\uF030',33697:'\uF031',33698:'\uF032',33699:'\uF033',33700:'\uF034',33701:'\uF035',33702:'\uF036',33703:'\uF037',33704:'\uF038',33705:'\uF039',33706:'\uF03A',33707:'\uF03B',33708:'\uF03C',33709:'\uF03D',33710:'\uF03E',33711:'\uF03F',33712:'\uF040',33713:'\uF041',33714:'\uF042',33715:'\uF043',33716:'\uF044',33717:'\uF045',33718:'\uF046',33719:'\uF047',33720:'\uF048',33721:'\uF049',33722:'\uF04A',33723:'\uF04B',33724:'\uF04C',33725:'\uF04D',33726:'\uF04E',33727:'\uF04F',33728:'\uF050',33729:'\uF051',33730:'\uF052',33731:'\uF053',33732:'\uF054',33733:'\uF055',33734:'\uF056',33735:'\uF057',33736:'\uF058',33737:'\uF059',33738:'\uF05A',33739:'\uF05B',33740:'\uF05C',33741:'\uF05D',33742:'\uF05E',33743:'\uF05F',33744:'\uF060',33745:'\uF061',33746:'\uF062',33747:'\uF063',33748:'\uF064',33749:'\uF065',33750:'\uF066',33751:'\uF067',33752:'\uF068',33753:'\uF069',33754:'\uF06A',33755:'\uF06B',33756:'\uF06C',33757:'\uF06D',33758:'\uF06E',33759:'\uF06F',33760:'\uF070',33761:'\uF071',33762:'\uF072',33763:'\uF073',33764:'\uF074',33765:'\uF075',33766:'\uF076',33767:'\uF077',33768:'\uF078',33769:'\uF079',33770:'\uF07A',33771:'\uF07B',33772:'\uF07C',33773:'\uF07D',33774:'\uF07E',33775:'\uF07F',33776:'\uF080',33777:'\uF081',33778:'\uF082',33779:'\uF083',33780:'\uF084',33781:'\uF085',33782:'\uF086',33783:'\uF087',33784:'\uF088',33785:'\uF089',33786:'\uF08A',33787:'\uF08B',33788:'\uF08C',33789:'\uF08D',33790:'\uF08E',33856:'\uF08F',33857:'\uF090',33858:'\uF091',33859:'\uF092',33860:'\uF093',33861:'\uF094',33862:'\uF095',33863:'\uF096',33864:'\uF097',33865:'\uF098',33866:'\uF099',33867:'\uF09A',33868:'\uF09B',33869:'\uF09C',33870:'\uF09D',33871:'\uF09E',33872:'\uF09F',33873:'\uF0A0',33874:'\uF0A1',33875:'\uF0A2',33876:'\uF0A3',33877:'\uF0A4',33878:'\uF0A5',33879:'\uF0A6',33880:'\uF0A7',33881:'\uF0A8',33882:'\uF0A9',33883:'\uF0AA',33884:'\uF0AB',33885:'\uF0AC',33886:'\uF0AD',33887:'\uF0AE',33888:'\uF0AF',33889:'\uF0B0',33890:'\uF0B1',33891:'\uF0B2',33892:'\uF0B3',33893:'\uF0B4',33894:'\uF0B5',33895:'\uF0B6',33896:'\uF0B7',33897:'\uF0B8',33898:'\uF0B9',33899:'\uF0BA',33900:'\uF0BB',33901:'\uF0BC',33902:'\uF0BD',33903:'\uF0BE',33904:'\uF0BF',33905:'\uF0C0',33906:'\uF0C1',33907:'\uF0C2',33908:'\uF0C3',33909:'\uF0C4',33910:'\uF0C5',33911:'\uF0C6',33912:'\uF0C7',33913:'\uF0C8',33914:'\uF0C9',33915:'\uF0CA',33916:'\uF0CB',33917:'\uF0CC',33918:'\uF0CD',33953:'\uF0CE',33954:'\uF0CF',33955:'\uF0D0',33956:'\uF0D1',33957:'\uF0D2',33958:'\uF0D3',33959:'\uF0D4',33960:'\uF0D5',33961:'\uF0D6',33962:'\uF0D7',33963:'\uF0D8',33964:'\uF0D9',33965:'\uF0DA',33966:'\uF0DB',33967:'\uF0DC',33968:'\uF0DD',33969:'\uF0DE',33970:'\uF0DF',33971:'\uF0E0',33972:'\uF0E1',33973:'\uF0E2',33974:'\uF0E3',33975:'\uF0E4',33976:'\uF0E5',33977:'\uF0E6',33978:'\uF0E7',33979:'\uF0E8',33980:'\uF0E9',33981:'\uF0EA',33982:'\uF0EB',33983:'\uF0EC',33984:'\uF0ED',33985:'\uF0EE',33986:'\uF0EF',33987:'\uF0F0',33988:'\uF0F1',33989:'\uF0F2',33990:'\uF0F3',33991:'\uF0F4',33992:'\uF0F5',33993:'\uF0F6',33994:'\uF0F7',33995:'\uF0F8',33996:'\uF0F9',33997:'\uF0FA',33998:'\uF0FB',33999:'\uF0FC',34000:'\uF0FD',34001:'\uF0FE',34002:'\uF0FF',34003:'\uF100',34004:'\uF101',34005:'\uF102',34006:'\uF103',34007:'\uF104',34008:'\uF105',34009:'\uF106',34010:'\uF107',34011:'\uF108',34012:'\uF109',34013:'\uF10A',34014:'\uF10B',34015:'\uF10C',34016:'\uF10D',34017:'\uF10E',34018:'\uF10F',34019:'\uF110',34020:'\uF111',34021:'\uF112',34022:'\uF113',34023:'\uF114',34024:'\uF115',34025:'\uF116',34026:'\uF117',34027:'\uF118',34028:'\uF119',34029:'\uF11A',34030:'\uF11B',34031:'\uF11C',34032:'\uF11D',34033:'\uF11E',34034:'\uF11F',34035:'\uF120',34036:'\uF121',34037:'\uF122',34038:'\uF123',34039:'\uF124',34040:'\uF125',34041:'\uF126',34042:'\uF127',34043:'\uF128',34044:'\uF129',34045:'\uF12A',34046:'\uF12B',34112:'\uF12C',34113:'\uF12D',34114:'\uF12E',34115:'\uF12F',34116:'\uF130',34117:'\uF131',34118:'\uF132',34119:'\uF133',34120:'\uF134',34121:'\uF135',34122:'\uF136',34123:'\uF137',34124:'\uF138',34125:'\uF139',34126:'\uF13A',34127:'\uF13B',34128:'\uF13C',34129:'\uF13D',34130:'\uF13E',34131:'\uF13F',34132:'\uF140',34133:'\uF141',34134:'\uF142',34135:'\uF143',34136:'\uF144',34137:'\uF145',34138:'\uF146',34139:'\uF147',34140:'\uF148',34141:'\uF149',34142:'\uF14A',34143:'\uF14B',34144:'\uF14C',34145:'\uF14D',34146:'\uF14E',34147:'\uF14F',34148:'\uF150',34149:'\uF151',34150:'\uF152',34151:'\uF153',34152:'\uF154',34153:'\uF155',34154:'\uF156',34155:'\uF157',34156:'\uF158',34157:'\uF159',34158:'\uF15A',34159:'\uF15B',34160:'\uF15C',34161:'\uF15D',34162:'\uF15E',34163:'\uF15F',34164:'\uF160',34165:'\uF161',34166:'\uF162',34167:'\uF163',34168:'\uF164',34169:'\uF165',34170:'\uF166',34171:'\uF167',34172:'\uF168',34173:'\uF169',34174:'\uF16A',34209:'\uF16B',34210:'\uF16C',34211:'\uF16D',34212:'\uF16E',34213:'\uF16F',34214:'\uF170',34215:'\uF171',34216:'\uF172',34217:'\uF173',34218:'\uF174',34219:'\uF175',34220:'\uF176',34221:'\uF177',34222:'\uF178',34223:'\uF179',34224:'\uF17A',34225:'\uF17B',34226:'\uF17C',34227:'\uF17D',34228:'\uF17E',34229:'\uF17F',34230:'\uF180',34231:'\uF181',34232:'\uF182',34233:'\uF183',34234:'\uF184',34235:'\uF185',34236:'\uF186',34237:'\uF187',34238:'\uF188',34239:'\uF189',34240:'\uF18A',34241:'\uF18B',34242:'\uF18C',34243:'\uF18D',34244:'\uF18E',34245:'\uF18F',34246:'\uF190',34247:'\uF191',34248:'\uF192',34249:'\uF193',34250:'\uF194',34251:'\uF195',34252:'\uF196',34253:'\uF197',34254:'\uF198',34255:'\uF199',34256:'\uF19A',34257:'\uF19B',34258:'\uF19C',34259:'\uF19D',34260:'\uF19E',34261:'\uF19F',34262:'\uF1A0',34263:'\uF1A1',34264:'\uF1A2',34265:'\uF1A3',34266:'\uF1A4',34267:'\uF1A5',34268:'\uF1A6',34269:'\uF1A7',34270:'\uF1A8',34271:'\uF1A9',34272:'\uF1AA',34273:'\uF1AB',34274:'\uF1AC',34275:'\uF1AD',34276:'\uF1AE',34277:'\uF1AF',34278:'\uF1B0',34279:'\uF1B1',34280:'\uF1B2',34281:'\uF1B3',34282:'\uF1B4',34283:'\uF1B5',34284:'\uF1B6',34285:'\uF1B7',34286:'\uF1B8',34287:'\uF1B9',34288:'\uF1BA',34289:'\uF1BB',34290:'\uF1BC',34291:'\uF1BD',34292:'\uF1BE',34293:'\uF1BF',34294:'\uF1C0',34295:'\uF1C1',34296:'\uF1C2',34297:'\uF1C3',34298:'\uF1C4',34299:'\uF1C5',34300:'\uF1C6',34301:'\uF1C7',34302:'\uF1C8',34368:'\uF1C9',34369:'\uF1CA',34370:'\uF1CB',34371:'\uF1CC',34372:'\uF1CD',34373:'\uF1CE',34374:'\uF1CF',34375:'\uF1D0',34376:'\uF1D1',34377:'\uF1D2',34378:'\uF1D3',34379:'\uF1D4',34380:'\uF1D5',34381:'\uF1D6',34382:'\uF1D7',34383:'\uF1D8',34384:'\uF1D9',34385:'\uF1DA',34386:'\uF1DB',34387:'\uF1DC',34388:'\uF1DD',34389:'\uF1DE',34390:'\uF1DF',34391:'\uF1E0',34392:'\uF1E1',34393:'\uF1E2',34394:'\uF1E3',34395:'\uF1E4',34396:'\uF1E5',34397:'\uF1E6',34398:'\uF1E7',34399:'\uF1E8',34400:'\uF1E9',34401:'\uF1EA',34402:'\uF1EB',34403:'\uF1EC',34404:'\uF1ED',34405:'\uF1EE',34406:'\uF1EF',34407:'\uF1F0',34408:'\uF1F1',34409:'\uF1F2',34410:'\uF1F3',34411:'\uF1F4',34412:'\uF1F5',34413:'\uF1F6',34414:'\uF1F7',34415:'\uF1F8',34416:'\uF1F9',34417:'\uF1FA',34418:'\uF1FB',34419:'\uF1FC',34420:'\uF1FD',34421:'\uF1FE',34422:'\uF1FF',34423:'\uF200',34424:'\uF201',34425:'\uF202',34426:'\uF203',34427:'\uF204',34428:'\uF205',34429:'\uF206',34430:'\uF207',34465:'\uF208',34466:'\uF209',34467:'\uF20A',34468:'\uF20B',34469:'\uF20C',34470:'\uF20D',34471:'\uF20E',34472:'\uF20F',34473:'\uF210',34474:'\uF211',34475:'\uF212',34476:'\uF213',34477:'\uF214',34478:'\uF215',34479:'\uF216',34480:'\uF217',34481:'\uF218',34482:'\uF219',34483:'\uF21A',34484:'\uF21B',34485:'\uF21C',34486:'\uF21D',34487:'\uF21E',34488:'\uF21F',34489:'\uF220',34490:'\uF221',34491:'\uF222',34492:'\uF223',34493:'\uF224',34494:'\uF225',34495:'\uF226',34496:'\uF227',34497:'\uF228',34498:'\uF229',34499:'\uF22A',34500:'\uF22B',34501:'\uF22C',34502:'\uF22D',34503:'\uF22E',34504:'\uF22F',34505:'\uF230',34506:'\uF231',34507:'\uF232',34508:'\uF233',34509:'\uF234',34510:'\uF235',34511:'\uF236',34512:'\uF237',34513:'\uF238',34514:'\uF239',34515:'\uF23A',34516:'\uF23B',34517:'\uF23C',34518:'\uF23D',34519:'\uF23E',34520:'\uF23F',34521:'\uF240',34522:'\uF241',34523:'\uF242',34524:'\uF243',34525:'\uF244',34526:'\uF245',34527:'\uF246',34528:'\uF247',34529:'\uF248',34530:'\uF249',34531:'\uF24A',34532:'\uF24B',34533:'\uF24C',34534:'\uF24D',34535:'\uF24E',34536:'\uF24F',34537:'\uF250',34538:'\uF251',34539:'\uF252',34540:'\uF253',34541:'\uF254',34542:'\uF255',34543:'\uF256',34544:'\uF257',34545:'\uF258',34546:'\uF259',34547:'\uF25A',34548:'\uF25B',34549:'\uF25C',34550:'\uF25D',34551:'\uF25E',34552:'\uF25F',34553:'\uF260',34554:'\uF261',34555:'\uF262',34556:'\uF263',34557:'\uF264',34558:'\uF265',34624:'\uF266',34625:'\uF267',34626:'\uF268',34627:'\uF269',34628:'\uF26A',34629:'\uF26B',34630:'\uF26C',34631:'\uF26D',34632:'\uF26E',34633:'\uF26F',34634:'\uF270',34635:'\uF271',34636:'\uF272',34637:'\uF273',34638:'\uF274',34639:'\uF275',34640:'\uF276',34641:'\uF277',34642:'\uF278',34643:'\uF279',34644:'\uF27A',34645:'\uF27B',34646:'\uF27C',34647:'\uF27D',34648:'\uF27E',34649:'\uF27F',34650:'\uF280',34651:'\uF281',34652:'\uF282',34653:'\uF283',34654:'\uF284',34655:'\uF285',34656:'\uF286',34657:'\uF287',34658:'\uF288',34659:'\uF289',34660:'\uF28A',34661:'\uF28B',34662:'\uF28C',34663:'\uF28D',34664:'\uF28E',34665:'\uF28F',34666:'\uF290',34667:'\uF291',34668:'\uF292',34669:'\uF293',34670:'\uF294',34671:'\uF295',34672:'\uF296',34673:'\uF297',34674:'\uF298',34675:'\uF299',34676:'\uF29A',34677:'\uF29B',34678:'\uF29C',34679:'\uF29D',34680:'\uF29E',34681:'\uF29F',34682:'\uF2A0',34683:'\uF2A1',34684:'\uF2A2',34685:'\uF2A3',34686:'\uF2A4',34721:'\uF2A5',34722:'\uF2A6',34723:'\uF2A7',34724:'\uF2A8',34725:'\uF2A9',34726:'\uF2AA',34727:'\uF2AB',34728:'\uF2AC',34729:'\uF2AD',34730:'\uF2AE',34731:'\uF2AF',34732:'\uF2B0',34733:'\uF2B1',34734:'\uF2B2',34735:'\uF2B3',34736:'\uF2B4',34737:'\uF2B5',34738:'\uF2B6',34739:'\uF2B7',34740:'\uF2B8',34741:'\uF2B9',34742:'\uF2BA',34743:'\uF2BB',34744:'\uF2BC',34745:'\uF2BD',34746:'\uF2BE',34747:'\uF2BF',34748:'\uF2C0',34749:'\uF2C1',34750:'\uF2C2',34751:'\uF2C3',34752:'\uF2C4',34753:'\uF2C5',34754:'\uF2C6',34755:'\uF2C7',34756:'\uF2C8',34757:'\uF2C9',34758:'\uF2CA',34759:'\uF2CB',34760:'\uF2CC',34761:'\uF2CD',34762:'\uF2CE',34763:'\uF2CF',34764:'\uF2D0',34765:'\uF2D1',34766:'\uF2D2',34767:'\uF2D3',34768:'\uF2D4',34769:'\uF2D5',34770:'\uF2D6',34771:'\uF2D7',34772:'\uF2D8',34773:'\uF2D9',34774:'\uF2DA',34775:'\uF2DB',34776:'\uF2DC',34777:'\uF2DD',34778:'\uF2DE',34779:'\uF2DF',34780:'\uF2E0',34781:'\uF2E1',34782:'\uF2E2',34783:'\uF2E3',34784:'\uF2E4',34785:'\uF2E5',34786:'\uF2E6',34787:'\uF2E7',34788:'\uF2E8',34789:'\uF2E9',34790:'\uF2EA',34791:'\uF2EB',34792:'\uF2EC',34793:'\uF2ED',34794:'\uF2EE',34795:'\uF2EF',34796:'\uF2F0',34797:'\uF2F1',34798:'\uF2F2',34799:'\uF2F3',34800:'\uF2F4',34801:'\uF2F5',34802:'\uF2F6',34803:'\uF2F7',34804:'\uF2F8',34805:'\uF2F9',34806:'\uF2FA',34807:'\uF2FB',34808:'\uF2FC',34809:'\uF2FD',34810:'\uF2FE',34811:'\uF2FF',34812:'\uF300',34813:'\uF301',34814:'\uF302',34880:'\uF303',34881:'\uF304',34882:'\uF305',34883:'\uF306',34884:'\uF307',34885:'\uF308',34886:'\uF309',34887:'\uF30A',34888:'\uF30B',34889:'\uF30C',34890:'\uF30D',34891:'\uF30E',34892:'\uF30F',34893:'\uF310',34894:'\uF311',34895:'\uF312',34896:'\uF313',34897:'\uF314',34898:'\uF315',34899:'\uF316',34900:'\uF317',34901:'\uF318',34902:'\uF319',34903:'\uF31A',34904:'\uF31B',34905:'\uF31C',34906:'\uF31D',34907:'\uF31E',34908:'\uF31F',34909:'\uF320',34910:'\uF321',34911:'\uF322',34912:'\uF323',34913:'\uF324',34914:'\uF325',34915:'\uF326',34916:'\uF327',34917:'\uF328',34918:'\uF329',34919:'\uF32A',34920:'\uF32B',34921:'\uF32C',34922:'\uF32D',34923:'\uF32E',34924:'\uF32F',34925:'\uF330',34926:'\uF331',34927:'\uF332',34928:'\uF333',34929:'\uF334',34930:'\uF335',34931:'\uF336',34932:'\uF337',34933:'\uF338',34934:'\uF339',34935:'\uF33A',34936:'\uF33B',34937:'\uF33C',34938:'\uF33D',34939:'\uF33E',34940:'\uF33F',34941:'\uF340',34942:'\uF341',34977:'\uF342',34978:'\uF343',34979:'\uF344',34980:'\uF345',34981:'\uF346',34982:'\uF347',34983:'\uF348',34984:'\uF349',34985:'\uF34A',34986:'\uF34B',34987:'\uF34C',34988:'\uF34D',34989:'\uF34E',34990:'\uF34F',34991:'\uF350',34992:'\uF351',34993:'\uF352',34994:'\uF353',34995:'\uF354',34996:'\uF355',34997:'\uF356',34998:'\uF357',34999:'\uF358',35000:'\uF359',35001:'\uF35A',35002:'\uF35B',35003:'\uF35C',35004:'\uF35D',35005:'\uF35E',35006:'\uF35F',35007:'\uF360',35008:'\uF361',35009:'\uF362',35010:'\uF363',35011:'\uF364',35012:'\uF365',35013:'\uF366',35014:'\uF367',35015:'\uF368',35016:'\uF369',35017:'\uF36A',35018:'\uF36B',35019:'\uF36C',35020:'\uF36D',35021:'\uF36E',35022:'\uF36F',35023:'\uF370',35024:'\uF371',35025:'\uF372',35026:'\uF373',35027:'\uF374',35028:'\uF375',35029:'\uF376',35030:'\uF377',35031:'\uF378',35032:'\uF379',35033:'\uF37A',35034:'\uF37B',35035:'\uF37C',35036:'\uF37D',35037:'\uF37E',35038:'\uF37F',35039:'\uF380',35040:'\uF381',35041:'\uF382',35042:'\uF383',35043:'\uF384',35044:'\uF385',35045:'\uF386',35046:'\uF387',35047:'\uF388',35048:'\uF389',35049:'\uF38A',35050:'\uF38B',35051:'\uF38C',35052:'\uF38D',35053:'\uF38E',35054:'\uF38F',35055:'\uF390',35056:'\uF391',35057:'\uF392',35058:'\uF393',35059:'\uF394',35060:'\uF395',35061:'\uF396',35062:'\uF397',35063:'\uF398',35064:'\uF399',35065:'\uF39A',35066:'\uF39B',35067:'\uF39C',35068:'\uF39D',35069:'\uF39E',35070:'\uF39F',35136:'\uF3A0',35137:'\uF3A1',35138:'\uF3A2',35139:'\uF3A3',35140:'\uF3A4',35141:'\uF3A5',35142:'\uF3A6',35143:'\uF3A7',35144:'\uF3A8',35145:'\uF3A9',35146:'\uF3AA',35147:'\uF3AB',35148:'\uF3AC',35149:'\uF3AD',35150:'\uF3AE',35151:'\uF3AF',35152:'\uF3B0',35153:'\uF3B1',35154:'\uF3B2',35155:'\uF3B3',35156:'\uF3B4',35157:'\uF3B5',35158:'\uF3B6',35159:'\uF3B7',35160:'\uF3B8',35161:'\uF3B9',35162:'\uF3BA',35163:'\uF3BB',35164:'\uF3BC',35165:'\uF3BD',35166:'\uF3BE',35167:'\uF3BF',35168:'\uF3C0',35169:'\uF3C1',35170:'\uF3C2',35171:'\uF3C3',35172:'\uF3C4',35173:'\uF3C5',35174:'\uF3C6',35175:'\uF3C7',35176:'\uF3C8',35177:'\uF3C9',35178:'\uF3CA',35179:'\uF3CB',35180:'\uF3CC',35181:'\uF3CD',35182:'\uF3CE',35183:'\uF3CF',35184:'\uF3D0',35185:'\uF3D1',35186:'\uF3D2',35187:'\uF3D3',35188:'\uF3D4',35189:'\uF3D5',35190:'\uF3D6',35191:'\uF3D7',35192:'\uF3D8',35193:'\uF3D9',35194:'\uF3DA',35195:'\uF3DB',35196:'\uF3DC',35197:'\uF3DD',35198:'\uF3DE',35233:'\uF3DF',35234:'\uF3E0',35235:'\uF3E1',35236:'\uF3E2',35237:'\uF3E3',35238:'\uF3E4',35239:'\uF3E5',35240:'\uF3E6',35241:'\uF3E7',35242:'\uF3E8',35243:'\uF3E9',35244:'\uF3EA',35245:'\uF3EB',35246:'\uF3EC',35247:'\uF3ED',35248:'\uF3EE',35249:'\uF3EF',35250:'\uF3F0',35251:'\uF3F1',35252:'\uF3F2',35253:'\uF3F3',35254:'\uF3F4',35255:'\uF3F5',35256:'\uF3F6',35257:'\uF3F7',35258:'\uF3F8',35259:'\uF3F9',35260:'\uF3FA',35261:'\uF3FB',35262:'\uF3FC',35263:'\uF3FD',35264:'\uF3FE',35265:'\uF3FF',35266:'\uF400',35267:'\uF401',35268:'\uF402',35269:'\uF403',35270:'\uF404',35271:'\uF405',35272:'\uF406',35273:'\uF407',35274:'\uF408',35275:'\uF409',35276:'\uF40A',35277:'\uF40B',35278:'\uF40C',35279:'\uF40D',35280:'\uF40E',35281:'\uF40F',35282:'\uF410',35283:'\uF411',35284:'\uF412',35285:'\uF413',35286:'\uF414',35287:'\uF415',35288:'\uF416',35289:'\uF417',35290:'\uF418',35291:'\uF419',35292:'\uF41A',35293:'\uF41B',35294:'\uF41C',35295:'\uF41D',35296:'\uF41E',35297:'\uF41F',35298:'\uF420',35299:'\uF421',35300:'\uF422',35301:'\uF423',35302:'\uF424',35303:'\uF425',35304:'\uF426',35305:'\uF427',35306:'\uF428',35307:'\uF429',35308:'\uF42A',35309:'\uF42B',35310:'\uF42C',35311:'\uF42D',35312:'\uF42E',35313:'\uF42F',35314:'\uF430',35315:'\uF431',35316:'\uF432',35317:'\uF433',35318:'\uF434',35319:'\uF435',35320:'\uF436',35321:'\uF437',35322:'\uF438',35323:'\uF439',35324:'\uF43A',35325:'\uF43B',35326:'\uF43C',35392:'\uF43D',35393:'\uF43E',35394:'\uF43F',35395:'\uF440',35396:'\uF441',35397:'\uF442',35398:'\uF443',35399:'\uF444',35400:'\uF445',35401:'\uF446',35402:'\uF447',35403:'\uF448',35404:'\uF449',35405:'\uF44A',35406:'\uF44B',35407:'\uF44C',35408:'\uF44D',35409:'\uF44E',35410:'\uF44F',35411:'\uF450',35412:'\uF451',35413:'\uF452',35414:'\uF453',35415:'\uF454',35416:'\uF455',35417:'\uF456',35418:'\uF457',35419:'\uF458',35420:'\uF459',35421:'\uF45A',35422:'\uF45B',35423:'\uF45C',35424:'\uF45D',35425:'\uF45E',35426:'\uF45F',35427:'\uF460',35428:'\uF461',35429:'\uF462',35430:'\uF463',35431:'\uF464',35432:'\uF465',35433:'\uF466',35434:'\uF467',35435:'\uF468',35436:'\uF469',35437:'\uF46A',35438:'\uF46B',35439:'\uF46C',35440:'\uF46D',35441:'\uF46E',35442:'\uF46F',35443:'\uF470',35444:'\uF471',35445:'\uF472',35446:'\uF473',35447:'\uF474',35448:'\uF475',35449:'\uF476',35450:'\uF477',35451:'\uF478',35452:'\uF479',35453:'\uF47A',35454:'\uF47B',35489:'\uF47C',35490:'\uF47D',35491:'\uF47E',35492:'\uF47F',35493:'\uF480',35494:'\uF481',35495:'\uF482',35496:'\uF483',35497:'\uF484',35498:'\uF485',35499:'\uF486',35500:'\uF487',35501:'\uF488',35502:'\uF489',35503:'\uF48A',35504:'\uF48B',35505:'\uF48C',35506:'\uF48D',35507:'\uF48E',35508:'\uF48F',35509:'\uF490',35510:'\uF491',35511:'\uF492',35512:'\uF493',35513:'\uF494',35514:'\uF495',35515:'\uF496',35516:'\uF497',35517:'\uF498',35518:'\uF499',35519:'\uF49A',35520:'\uF49B',35521:'\uF49C',35522:'\uF49D',35523:'\uF49E',35524:'\uF49F',35525:'\uF4A0',35526:'\uF4A1',35527:'\uF4A2',35528:'\uF4A3',35529:'\uF4A4',35530:'\uF4A5',35531:'\uF4A6',35532:'\uF4A7',35533:'\uF4A8',35534:'\uF4A9',35535:'\uF4AA',35536:'\uF4AB',35537:'\uF4AC',35538:'\uF4AD',35539:'\uF4AE',35540:'\uF4AF',35541:'\uF4B0',35542:'\uF4B1',35543:'\uF4B2',35544:'\uF4B3',35545:'\uF4B4',35546:'\uF4B5',35547:'\uF4B6',35548:'\uF4B7',35549:'\uF4B8',35550:'\uF4B9',35551:'\uF4BA',35552:'\uF4BB',35553:'\uF4BC',35554:'\uF4BD',35555:'\uF4BE',35556:'\uF4BF',35557:'\uF4C0',35558:'\uF4C1',35559:'\uF4C2',35560:'\uF4C3',35561:'\uF4C4',35562:'\uF4C5',35563:'\uF4C6',35564:'\uF4C7',35565:'\uF4C8',35566:'\uF4C9',35567:'\uF4CA',35568:'\uF4CB',35569:'\uF4CC',35570:'\uF4CD',35571:'\uF4CE',35572:'\uF4CF',35573:'\uF4D0',35574:'\uF4D1',35575:'\uF4D2',35576:'\uF4D3',35577:'\uF4D4',35578:'\uF4D5',35579:'\uF4D6',35580:'\uF4D7',35581:'\uF4D8',35582:'\uF4D9',35648:'\uF4DA',35649:'\uF4DB',35650:'\uF4DC',35651:'\uF4DD',35652:'\uF4DE',35653:'\uF4DF',35654:'\uF4E0',35655:'\uF4E1',35656:'\uF4E2',35657:'\uF4E3',35658:'\uF4E4',35659:'\uF4E5',35660:'\uF4E6',35661:'\uF4E7',35662:'\uF4E8',35663:'\uF4E9',35664:'\uF4EA',35665:'\uF4EB',35666:'\uF4EC',35667:'\uF4ED',35668:'\uF4EE',35669:'\uF4EF',35670:'\uF4F0',35671:'\uF4F1',35672:'\uF4F2',35673:'\uF4F3',35674:'\uF4F4',35675:'\uF4F5',35676:'\uF4F6',35677:'\uF4F7',35678:'\uF4F8',35679:'\uF4F9',35680:'\uF4FA',35681:'\uF4FB',35682:'\uF4FC',35683:'\uF4FD',35684:'\uF4FE',35685:'\uF4FF',35686:'\uF500',35687:'\uF501',35688:'\uF502',35689:'\uF503',35690:'\uF504',35691:'\uF505',35692:'\uF506',35693:'\uF507',35694:'\uF508',35695:'\uF509',35696:'\uF50A',35697:'\uF50B',35698:'\uF50C',35699:'\uF50D',35700:'\uF50E',35701:'\uF50F',35702:'\uF510',35703:'\uF511',35704:'\uF512',35705:'\uF513',35706:'\uF514',35707:'\uF515',35708:'\uF516',35709:'\uF517',35710:'\uF518',35745:'\uF519',35746:'\uF51A',35747:'\uF51B',35748:'\uF51C',35749:'\uF51D',35750:'\uF51E',35751:'\uF51F',35752:'\uF520',35753:'\uF521',35754:'\uF522',35755:'\uF523',35756:'\uF524',35757:'\uF525',35758:'\uF526',35759:'\uF527',35760:'\uF528',35761:'\uF529',35762:'\uF52A',35763:'\uF52B',35764:'\uF52C',35765:'\uF52D',35766:'\uF52E',35767:'\uF52F',35768:'\uF530',35769:'\uF531',35770:'\uF532',35771:'\uF533',35772:'\uF534',35773:'\uF535',35774:'\uF536',35775:'\uF537',35776:'\uF538',35777:'\uF539',35778:'\uF53A',35779:'\uF53B',35780:'\uF53C',35781:'\uF53D',35782:'\uF53E',35783:'\uF53F',35784:'\uF540',35785:'\uF541',35786:'\uF542',35787:'\uF543',35788:'\uF544',35789:'\uF545',35790:'\uF546',35791:'\uF547',35792:'\uF548',35793:'\uF549',35794:'\uF54A',35795:'\uF54B',35796:'\uF54C',35797:'\uF54D',35798:'\uF54E',35799:'\uF54F',35800:'\uF550',35801:'\uF551',35802:'\uF552',35803:'\uF553',35804:'\uF554',35805:'\uF555',35806:'\uF556',35807:'\uF557',35808:'\uF558',35809:'\uF559',35810:'\uF55A',35811:'\uF55B',35812:'\uF55C',35813:'\uF55D',35814:'\uF55E',35815:'\uF55F',35816:'\uF560',35817:'\uF561',35818:'\uF562',35819:'\uF563',35820:'\uF564',35821:'\uF565',35822:'\uF566',35823:'\uF567',35824:'\uF568',35825:'\uF569',35826:'\uF56A',35827:'\uF56B',35828:'\uF56C',35829:'\uF56D',35830:'\uF56E',35831:'\uF56F',35832:'\uF570',35833:'\uF571',35834:'\uF572',35835:'\uF573',35836:'\uF574',35837:'\uF575',35838:'\uF576',35904:'\uF577',35905:'\uF578',35906:'\uF579',35907:'\uF57A',35908:'\uF57B',35909:'\uF57C',35910:'\uF57D',35911:'\uF57E',35912:'\uF57F',35913:'\uF580',35914:'\uF581',35915:'\uF582',35916:'\uF583',35917:'\uF584',35918:'\uF585',35919:'\uF586',35920:'\uF587',35921:'\uF588',35922:'\uF589',35923:'\uF58A',35924:'\uF58B',35925:'\uF58C',35926:'\uF58D',35927:'\uF58E',35928:'\uF58F',35929:'\uF590',35930:'\uF591',35931:'\uF592',35932:'\uF593',35933:'\uF594',35934:'\uF595',35935:'\uF596',35936:'\uF597',35937:'\uF598',35938:'\uF599',35939:'\uF59A',35940:'\uF59B',35941:'\uF59C',35942:'\uF59D',35943:'\uF59E',35944:'\uF59F',35945:'\uF5A0',35946:'\uF5A1',35947:'\uF5A2',35948:'\uF5A3',35949:'\uF5A4',35950:'\uF5A5',35951:'\uF5A6',35952:'\uF5A7',35953:'\uF5A8',35954:'\uF5A9',35955:'\uF5AA',35956:'\uF5AB',35957:'\uF5AC',35958:'\uF5AD',35959:'\uF5AE',35960:'\uF5AF',35961:'\uF5B0',35962:'\uF5B1',35963:'\uF5B2',35964:'\uF5B3',35965:'\uF5B4',35966:'\uF5B5',36001:'\uF5B6',36002:'\uF5B7',36003:'\uF5B8',36004:'\uF5B9',36005:'\uF5BA',36006:'\uF5BB',36007:'\uF5BC',36008:'\uF5BD',36009:'\uF5BE',36010:'\uF5BF',36011:'\uF5C0',36012:'\uF5C1',36013:'\uF5C2',36014:'\uF5C3',36015:'\uF5C4',36016:'\uF5C5',36017:'\uF5C6',36018:'\uF5C7',36019:'\uF5C8',36020:'\uF5C9',36021:'\uF5CA',36022:'\uF5CB',36023:'\uF5CC',36024:'\uF5CD',36025:'\uF5CE',36026:'\uF5CF',36027:'\uF5D0',36028:'\uF5D1',36029:'\uF5D2',36030:'\uF5D3',36031:'\uF5D4',36032:'\uF5D5',36033:'\uF5D6',36034:'\uF5D7',36035:'\uF5D8',36036:'\uF5D9',36037:'\uF5DA',36038:'\uF5DB',36039:'\uF5DC',36040:'\uF5DD',36041:'\uF5DE',36042:'\uF5DF',36043:'\uF5E0',36044:'\uF5E1',36045:'\uF5E2',36046:'\uF5E3',36047:'\uF5E4',36048:'\uF5E5',36049:'\uF5E6',36050:'\uF5E7',36051:'\uF5E8',36052:'\uF5E9',36053:'\uF5EA',36054:'\uF5EB',36055:'\uF5EC',36056:'\uF5ED',36057:'\uF5EE',36058:'\uF5EF',36059:'\uF5F0',36060:'\uF5F1',36061:'\uF5F2',36062:'\uF5F3',36063:'\uF5F4',36064:'\uF5F5',36065:'\uF5F6',36066:'\uF5F7',36067:'\uF5F8',36068:'\uF5F9',36069:'\uF5FA',36070:'\uF5FB',36071:'\uF5FC',36072:'\uF5FD',36073:'\uF5FE',36074:'\uF5FF',36075:'\uF600',36076:'\uF601',36077:'\uF602',36078:'\uF603',36079:'\uF604',36080:'\uF605',36081:'\uF606',36082:'\uF607',36083:'\uF608',36084:'\uF609',36085:'\uF60A',36086:'\uF60B',36087:'\uF60C',36088:'\uF60D',36089:'\uF60E',36090:'\uF60F',36091:'\uF610',36092:'\uF611',36093:'\uF612',36094:'\uF613',36160:'\uF614',36161:'\uF615',36162:'\uF616',36163:'\uF617',36164:'\uF618',36165:'\uF619',36166:'\uF61A',36167:'\uF61B',36168:'\uF61C',36169:'\uF61D',36170:'\uF61E',36171:'\uF61F',36172:'\uF620',36173:'\uF621',36174:'\uF622',36175:'\uF623',36176:'\uF624',36177:'\uF625',36178:'\uF626',36179:'\uF627',36180:'\uF628',36181:'\uF629',36182:'\uF62A',36183:'\uF62B',36184:'\uF62C',36185:'\uF62D',36186:'\uF62E',36187:'\uF62F',36188:'\uF630',36189:'\uF631',36190:'\uF632',36191:'\uF633',36192:'\uF634',36193:'\uF635',36194:'\uF636',36195:'\uF637',36196:'\uF638',36197:'\uF639',36198:'\uF63A',36199:'\uF63B',36200:'\uF63C',36201:'\uF63D',36202:'\uF63E',36203:'\uF63F',36204:'\uF640',36205:'\uF641',36206:'\uF642',36207:'\uF643',36208:'\uF644',36209:'\uF645',36210:'\uF646',36211:'\uF647',36212:'\uF648',36213:'\uF649',36214:'\uF64A',36215:'\uF64B',36216:'\uF64C',36217:'\uF64D',36218:'\uF64E',36219:'\uF64F',36220:'\uF650',36221:'\uF651',36222:'\uF652',36257:'\uF653',36258:'\uF654',36259:'\uF655',36260:'\uF656',36261:'\uF657',36262:'\uF658',36263:'\uF659',36264:'\uF65A',36265:'\uF65B',36266:'\uF65C',36267:'\uF65D',36268:'\uF65E',36269:'\uF65F',36270:'\uF660',36271:'\uF661',36272:'\uF662',36273:'\uF663',36274:'\uF664',36275:'\uF665',36276:'\uF666',36277:'\uF667',36278:'\uF668',36279:'\uF669',36280:'\uF66A',36281:'\uF66B',36282:'\uF66C',36283:'\uF66D',36284:'\uF66E',36285:'\uF66F',36286:'\uF670',36287:'\uF671',36288:'\uF672',36289:'\uF673',36290:'\uF674',36291:'\uF675',36292:'\uF676',36293:'\uF677',36294:'\uF678',36295:'\uF679',36296:'\uF67A',36297:'\uF67B',36298:'\uF67C',36299:'\uF67D',36300:'\uF67E',36301:'\uF67F',36302:'\uF680',36303:'\uF681',36304:'\uF682',36305:'\uF683',36306:'\uF684',36307:'\uF685',36308:'\uF686',36309:'\uF687',36310:'\uF688',36311:'\uF689',36312:'\uF68A',36313:'\uF68B',36314:'\uF68C',36315:'\uF68D',36316:'\uF68E',36317:'\uF68F',36318:'\uF690',36319:'\uF691',36320:'\uF692',36321:'\uF693',36322:'\uF694',36323:'\uF695',36324:'\uF696',36325:'\uF697',36326:'\uF698',36327:'\uF699',36328:'\uF69A',36329:'\uF69B',36330:'\uF69C',36331:'\uF69D',36332:'\uF69E',36333:'\uF69F',36334:'\uF6A0',36335:'\uF6A1',36336:'\uF6A2',36337:'\uF6A3',36338:'\uF6A4',36339:'\uF6A5',36340:'\uF6A6',36341:'\uF6A7',36342:'\uF6A8',36343:'\uF6A9',36344:'\uF6AA',36345:'\uF6AB',36346:'\uF6AC',36347:'\uF6AD',36348:'\uF6AE',36349:'\uF6AF',36350:'\uF6B0',36416:'\uE311',36417:'\uE312',36418:'\uE313',36419:'\uE314',36420:'\uE315',36421:'\uE316',36422:'\uE317',36423:'\uE318',36424:'\uE319',36425:'\uE31A',36426:'\uE31B',36427:'\uE31C',36428:'\uE31D',36429:'\uE31E',36430:'\uE31F',36431:'\uE320',36432:'\uE321',36433:'\uE322',36434:'\uE323',36435:'\uE324',36436:'\uE325',36437:'\uE326',36438:'\uE327',36439:'\uE328',36440:'\uE329',36441:'\uE32A',36442:'\uE32B',36443:'\uE32C',36444:'\uE32D',36445:'\uE32E',36446:'\uE32F',36447:'\uE330',36448:'\uE331',36449:'\uE332',36450:'\uE333',36451:'\uE334',36452:'\uE335',36453:'\uE336',36454:'\uE337',36455:'\uE338',36456:'\uE339',36457:'\uE33A',36458:'\uE33B',36459:'\uE33C',36460:'\uE33D',36461:'\uE33E',36462:'\uE33F',36463:'\uE340',36464:'\uE341',36465:'\uE342',36466:'\uE343',36467:'\uE344',36468:'\uE345',36469:'\uE346',36470:'\uE347',36471:'\uE348',36472:'\uE349',36473:'\uE34A',36474:'\uE34B',36475:'\uE34C',36476:'\uE34D',36477:'\uE34E',36478:'\uE34F',36513:'\uE350',36514:'\uE351',36515:'\uE352',36516:'\uE353',36517:'\uE354',36518:'\uE355',36519:'\uE356',36520:'\uE357',36521:'\uE358',36522:'\uE359',36523:'\uE35A',36524:'\uE35B',36525:'\uE35C',36526:'\uE35D',36527:'\uE35E',36528:'\uE35F',36529:'\uE360',36530:'\uE361',36531:'\uE362',36532:'\uE363',36533:'\uE364',36534:'\uE365',36535:'\uE366',36536:'\uE367',36537:'\uE368',36538:'\uE369',36539:'\uE36A',36540:'\uE36B',36541:'\uE36C',36542:'\uE36D',36543:'\uE36E',36544:'\uE36F',36545:'\uE370',36546:'\uE371',36547:'\uE372',36548:'\uE373',36549:'\uE374',36550:'\uE375',36551:'\uE376',36552:'\uE377',36553:'\uE378',36554:'\uE379',36555:'\uE37A',36556:'\uE37B',36557:'\uE37C',36558:'\uE37D',36559:'\uE37E',36560:'\uE37F',36561:'\uE380',36562:'\uE381',36563:'\uE382',36564:'\uE383',36565:'\uE384',36566:'\uE385',36567:'\uE386',36568:'\uE387',36569:'\uE388',36570:'\uE389',36571:'\uE38A',36572:'\uE38B',36573:'\uE38C',36574:'\uE38D',36575:'\uE38E',36576:'\uE38F',36577:'\uE390',36578:'\uE391',36579:'\uE392',36580:'\uE393',36581:'\uE394',36582:'\uE395',36583:'\uE396',36584:'\uE397',36585:'\uE398',36586:'\uE399',36587:'\uE39A',36588:'\uE39B',36589:'\uE39C',36590:'\uE39D',36591:'\uE39E',36592:'\uE39F',36593:'\uE3A0',36594:'\uE3A1',36595:'\uE3A2',36596:'\uE3A3',36597:'\uE3A4',36598:'\uE3A5',36599:'\uE3A6',36600:'\uE3A7',36601:'\uE3A8',36602:'\uE3A9',36603:'\uE3AA',36604:'\uE3AB',36605:'\uE3AC',36606:'\uE3AD',36672:'\uE3AE',36673:'\uE3AF',36674:'\uE3B0',36675:'\uE3B1',36676:'\uE3B2',36677:'\uE3B3',36678:'\uE3B4',36679:'\uE3B5',36680:'\uE3B6',36681:'\uE3B7',36682:'\uE3B8',36683:'\uE3B9',36684:'\uE3BA',36685:'\uE3BB',36686:'\uE3BC',36687:'\uE3BD',36688:'\uE3BE',36689:'\uE3BF',36690:'\uE3C0',36691:'\uE3C1',36692:'\uE3C2',36693:'\uE3C3',36694:'\uE3C4',36695:'\uE3C5',36696:'\uE3C6',36697:'\uE3C7',36698:'\uE3C8',36699:'\uE3C9',36700:'\uE3CA',36701:'\uE3CB',36702:'\uE3CC',36703:'\uE3CD',36704:'\uE3CE',36705:'\uE3CF',36706:'\uE3D0',36707:'\uE3D1',36708:'\uE3D2',36709:'\uE3D3',36710:'\uE3D4',36711:'\uE3D5',36712:'\uE3D6',36713:'\uE3D7',36714:'\uE3D8',36715:'\uE3D9',36716:'\uE3DA',36717:'\uE3DB',36718:'\uE3DC',36719:'\uE3DD',36720:'\uE3DE',36721:'\uE3DF',36722:'\uE3E0',36723:'\uE3E1',36724:'\uE3E2',36725:'\uE3E3',36726:'\uE3E4',36727:'\uE3E5',36728:'\uE3E6',36729:'\uE3E7',36730:'\uE3E8',36731:'\uE3E9',36732:'\uE3EA',36733:'\uE3EB',36734:'\uE3EC',36769:'\uE3ED',36770:'\uE3EE',36771:'\uE3EF',36772:'\uE3F0',36773:'\uE3F1',36774:'\uE3F2',36775:'\uE3F3',36776:'\uE3F4',36777:'\uE3F5',36778:'\uE3F6',36779:'\uE3F7',36780:'\uE3F8',36781:'\uE3F9',36782:'\uE3FA',36783:'\uE3FB',36784:'\uE3FC',36785:'\uE3FD',36786:'\uE3FE',36787:'\uE3FF',36788:'\uE400',36789:'\uE401',36790:'\uE402',36791:'\uE403',36792:'\uE404',36793:'\uE405',36794:'\uE406',36795:'\uE407',36796:'\uE408',36797:'\uE409',36798:'\uE40A',36799:'\uE40B',36800:'\uE40C',36801:'\uE40D',36802:'\uE40E',36803:'\uE40F',36804:'\uE410',36805:'\uE411',36806:'\uE412',36807:'\uE413',36808:'\uE414',36809:'\uE415',36810:'\uE416',36811:'\uE417',36812:'\uE418',36813:'\uE419',36814:'\uE41A',36815:'\uE41B',36816:'\uE41C',36817:'\uE41D',36818:'\uE41E',36819:'\uE41F',36820:'\uE420',36821:'\uE421',36822:'\uE422',36823:'\uE423',36824:'\uE424',36825:'\uE425',36826:'\uE426',36827:'\uE427',36828:'\uE428',36829:'\uE429',36830:'\uE42A',36831:'\uE42B',36832:'\uE42C',36833:'\uE42D',36834:'\uE42E',36835:'\uE42F',36836:'\uE430',36837:'\uE431',36838:'\uE432',36839:'\uE433',36840:'\uE434',36841:'\uE435',36842:'\uE436',36843:'\uE437',36844:'\uE438',36845:'\uE439',36846:'\uE43A',36847:'\uE43B',36848:'\uE43C',36849:'\uE43D',36850:'\uE43E',36851:'\uE43F',36852:'\uE440',36853:'\uE441',36854:'\uE442',36855:'\uE443',36856:'\uE444',36857:'\uE445',36858:'\uE446',36859:'\uE447',36860:'\uE448',36861:'\uE449',36862:'\uE44A',36928:'\uE44B',36929:'\uE44C',36930:'\uE44D',36931:'\uE44E',36932:'\uE44F',36933:'\uE450',36934:'\uE451',36935:'\uE452',36936:'\uE453',36937:'\uE454',36938:'\uE455',36939:'\uE456',36940:'\uE457',36941:'\uE458',36942:'\uE459',36943:'\uE45A',36944:'\uE45B',36945:'\uE45C',36946:'\uE45D',36947:'\uE45E',36948:'\uE45F',36949:'\uE460',36950:'\uE461',36951:'\uE462',36952:'\uE463',36953:'\uE464',36954:'\uE465',36955:'\uE466',36956:'\uE467',36957:'\uE468',36958:'\uE469',36959:'\uE46A',36960:'\uE46B',36961:'\uE46C',36962:'\uE46D',36963:'\uE46E',36964:'\uE46F',36965:'\uE470',36966:'\uE471',36967:'\uE472',36968:'\uE473',36969:'\uE474',36970:'\uE475',36971:'\uE476',36972:'\uE477',36973:'\uE478',36974:'\uE479',36975:'\uE47A',36976:'\uE47B',36977:'\uE47C',36978:'\uE47D',36979:'\uE47E',36980:'\uE47F',36981:'\uE480',36982:'\uE481',36983:'\uE482',36984:'\uE483',36985:'\uE484',36986:'\uE485',36987:'\uE486',36988:'\uE487',36989:'\uE488',36990:'\uE489',37025:'\uE48A',37026:'\uE48B',37027:'\uE48C',37028:'\uE48D',37029:'\uE48E',37030:'\uE48F',37031:'\uE490',37032:'\uE491',37033:'\uE492',37034:'\uE493',37035:'\uE494',37036:'\uE495',37037:'\uE496',37038:'\uE497',37039:'\uE498',37040:'\uE499',37041:'\uE49A',37042:'\uE49B',37043:'\uE49C',37044:'\uE49D',37045:'\uE49E',37046:'\uE49F',37047:'\uE4A0',37048:'\uE4A1',37049:'\uE4A2',37050:'\uE4A3',37051:'\uE4A4',37052:'\uE4A5',37053:'\uE4A6',37054:'\uE4A7',37055:'\uE4A8',37056:'\uE4A9',37057:'\uE4AA',37058:'\uE4AB',37059:'\uE4AC',37060:'\uE4AD',37061:'\uE4AE',37062:'\uE4AF',37063:'\uE4B0',37064:'\uE4B1',37065:'\uE4B2',37066:'\uE4B3',37067:'\uE4B4',37068:'\uE4B5',37069:'\uE4B6',37070:'\uE4B7',37071:'\uE4B8',37072:'\uE4B9',37073:'\uE4BA',37074:'\uE4BB',37075:'\uE4BC',37076:'\uE4BD',37077:'\uE4BE',37078:'\uE4BF',37079:'\uE4C0',37080:'\uE4C1',37081:'\uE4C2',37082:'\uE4C3',37083:'\uE4C4',37084:'\uE4C5',37085:'\uE4C6',37086:'\uE4C7',37087:'\uE4C8',37088:'\uE4C9',37089:'\uE4CA',37090:'\uE4CB',37091:'\uE4CC',37092:'\uE4CD',37093:'\uE4CE',37094:'\uE4CF',37095:'\uE4D0',37096:'\uE4D1',37097:'\uE4D2',37098:'\uE4D3',37099:'\uE4D4',37100:'\uE4D5',37101:'\uE4D6',37102:'\uE4D7',37103:'\uE4D8',37104:'\uE4D9',37105:'\uE4DA',37106:'\uE4DB',37107:'\uE4DC',37108:'\uE4DD',37109:'\uE4DE',37110:'\uE4DF',37111:'\uE4E0',37112:'\uE4E1',37113:'\uE4E2',37114:'\uE4E3',37115:'\uE4E4',37116:'\uE4E5',37117:'\uE4E6',37118:'\uE4E7',37184:'\uE4E8',37185:'\uE4E9',37186:'\uE4EA',37187:'\uE4EB',37188:'\uE4EC',37189:'\uE4ED',37190:'\uE4EE',37191:'\uE4EF',37192:'\uE4F0',37193:'\uE4F1',37194:'\uE4F2',37195:'\uE4F3',37196:'\uE4F4',37197:'\uE4F5',37198:'\uE4F6',37199:'\uE4F7',37200:'\uE4F8',37201:'\uE4F9',37202:'\uE4FA',37203:'\uE4FB',37204:'\uE4FC',37205:'\uE4FD',37206:'\uE4FE',37207:'\uE4FF',37208:'\uE500',37209:'\uE501',37210:'\uE502',37211:'\uE503',37212:'\uE504',37213:'\uE505',37214:'\uE506',37215:'\uE507',37216:'\uE508',37217:'\uE509',37218:'\uE50A',37219:'\uE50B',37220:'\uE50C',37221:'\uE50D',37222:'\uE50E',37223:'\uE50F',37224:'\uE510',37225:'\uE511',37226:'\uE512',37227:'\uE513',37228:'\uE514',37229:'\uE515',37230:'\uE516',37231:'\uE517',37232:'\uE518',37233:'\uE519',37234:'\uE51A',37235:'\uE51B',37236:'\uE51C',37237:'\uE51D',37238:'\uE51E',37239:'\uE51F',37240:'\uE520',37241:'\uE521',37242:'\uE522',37243:'\uE523',37244:'\uE524',37245:'\uE525',37246:'\uE526',37281:'\uE527',37282:'\uE528',37283:'\uE529',37284:'\uE52A',37285:'\uE52B',37286:'\uE52C',37287:'\uE52D',37288:'\uE52E',37289:'\uE52F',37290:'\uE530',37291:'\uE531',37292:'\uE532',37293:'\uE533',37294:'\uE534',37295:'\uE535',37296:'\uE536',37297:'\uE537',37298:'\uE538',37299:'\uE539',37300:'\uE53A',37301:'\uE53B',37302:'\uE53C',37303:'\uE53D',37304:'\uE53E',37305:'\uE53F',37306:'\uE540',37307:'\uE541',37308:'\uE542',37309:'\uE543',37310:'\uE544',37311:'\uE545',37312:'\uE546',37313:'\uE547',37314:'\uE548',37315:'\uE549',37316:'\uE54A',37317:'\uE54B',37318:'\uE54C',37319:'\uE54D',37320:'\uE54E',37321:'\uE54F',37322:'\uE550',37323:'\uE551',37324:'\uE552',37325:'\uE553',37326:'\uE554',37327:'\uE555',37328:'\uE556',37329:'\uE557',37330:'\uE558',37331:'\uE559',37332:'\uE55A',37333:'\uE55B',37334:'\uE55C',37335:'\uE55D',37336:'\uE55E',37337:'\uE55F',37338:'\uE560',37339:'\uE561',37340:'\uE562',37341:'\uE563',37342:'\uE564',37343:'\uE565',37344:'\uE566',37345:'\uE567',37346:'\uE568',37347:'\uE569',37348:'\uE56A',37349:'\uE56B',37350:'\uE56C',37351:'\uE56D',37352:'\uE56E',37353:'\uE56F',37354:'\uE570',37355:'\uE571',37356:'\uE572',37357:'\uE573',37358:'\uE574',37359:'\uE575',37360:'\uE576',37361:'\uE577',37362:'\uE578',37363:'\uE579',37364:'\uE57A',37365:'\uE57B',37366:'\uE57C',37367:'\uE57D',37368:'\uE57E',37369:'\uE57F',37370:'\uE580',37371:'\uE581',37372:'\uE582',37373:'\uE583',37374:'\uE584',37440:'\uE585',37441:'\uE586',37442:'\uE587',37443:'\uE588',37444:'\uE589',37445:'\uE58A',37446:'\uE58B',37447:'\uE58C',37448:'\uE58D',37449:'\uE58E',37450:'\uE58F',37451:'\uE590',37452:'\uE591',37453:'\uE592',37454:'\uE593',37455:'\uE594',37456:'\uE595',37457:'\uE596',37458:'\uE597',37459:'\uE598',37460:'\uE599',37461:'\uE59A',37462:'\uE59B',37463:'\uE59C',37464:'\uE59D',37465:'\uE59E',37466:'\uE59F',37467:'\uE5A0',37468:'\uE5A1',37469:'\uE5A2',37470:'\uE5A3',37471:'\uE5A4',37472:'\uE5A5',37473:'\uE5A6',37474:'\uE5A7',37475:'\uE5A8',37476:'\uE5A9',37477:'\uE5AA',37478:'\uE5AB',37479:'\uE5AC',37480:'\uE5AD',37481:'\uE5AE',37482:'\uE5AF',37483:'\uE5B0',37484:'\uE5B1',37485:'\uE5B2',37486:'\uE5B3',37487:'\uE5B4',37488:'\uE5B5',37489:'\uE5B6',37490:'\uE5B7',37491:'\uE5B8',37492:'\uE5B9',37493:'\uE5BA',37494:'\uE5BB',37495:'\uE5BC',37496:'\uE5BD',37497:'\uE5BE',37498:'\uE5BF',37499:'\uE5C0',37500:'\uE5C1',37501:'\uE5C2',37502:'\uE5C3',37537:'\uE5C4',37538:'\uE5C5',37539:'\uE5C6',37540:'\uE5C7',37541:'\uE5C8',37542:'\uE5C9',37543:'\uE5CA',37544:'\uE5CB',37545:'\uE5CC',37546:'\uE5CD',37547:'\uE5CE',37548:'\uE5CF',37549:'\uE5D0',37550:'\uE5D1',37551:'\uE5D2',37552:'\uE5D3',37553:'\uE5D4',37554:'\uE5D5',37555:'\uE5D6',37556:'\uE5D7',37557:'\uE5D8',37558:'\uE5D9',37559:'\uE5DA',37560:'\uE5DB',37561:'\uE5DC',37562:'\uE5DD',37563:'\uE5DE',37564:'\uE5DF',37565:'\uE5E0',37566:'\uE5E1',37567:'\uE5E2',37568:'\uE5E3',37569:'\uE5E4',37570:'\uE5E5',37571:'\uE5E6',37572:'\uE5E7',37573:'\uE5E8',37574:'\uE5E9',37575:'\uE5EA',37576:'\uE5EB',37577:'\uE5EC',37578:'\uE5ED',37579:'\uE5EE',37580:'\uE5EF',37581:'\uE5F0',37582:'\uE5F1',37583:'\uE5F2',37584:'\uE5F3',37585:'\uE5F4',37586:'\uE5F5',37587:'\uE5F6',37588:'\uE5F7',37589:'\uE5F8',37590:'\uE5F9',37591:'\uE5FA',37592:'\uE5FB',37593:'\uE5FC',37594:'\uE5FD',37595:'\uE5FE',37596:'\uE5FF',37597:'\uE600',37598:'\uE601',37599:'\uE602',37600:'\uE603',37601:'\uE604',37602:'\uE605',37603:'\uE606',37604:'\uE607',37605:'\uE608',37606:'\uE609',37607:'\uE60A',37608:'\uE60B',37609:'\uE60C',37610:'\uE60D',37611:'\uE60E',37612:'\uE60F',37613:'\uE610',37614:'\uE611',37615:'\uE612',37616:'\uE613',37617:'\uE614',37618:'\uE615',37619:'\uE616',37620:'\uE617',37621:'\uE618',37622:'\uE619',37623:'\uE61A',37624:'\uE61B',37625:'\uE61C',37626:'\uE61D',37627:'\uE61E',37628:'\uE61F',37629:'\uE620',37630:'\uE621',37696:'\uE622',37697:'\uE623',37698:'\uE624',37699:'\uE625',37700:'\uE626',37701:'\uE627',37702:'\uE628',37703:'\uE629',37704:'\uE62A',37705:'\uE62B',37706:'\uE62C',37707:'\uE62D',37708:'\uE62E',37709:'\uE62F',37710:'\uE630',37711:'\uE631',37712:'\uE632',37713:'\uE633',37714:'\uE634',37715:'\uE635',37716:'\uE636',37717:'\uE637',37718:'\uE638',37719:'\uE639',37720:'\uE63A',37721:'\uE63B',37722:'\uE63C',37723:'\uE63D',37724:'\uE63E',37725:'\uE63F',37726:'\uE640',37727:'\uE641',37728:'\uE642',37729:'\uE643',37730:'\uE644',37731:'\uE645',37732:'\uE646',37733:'\uE647',37734:'\uE648',37735:'\uE649',37736:'\uE64A',37737:'\uE64B',37738:'\uE64C',37739:'\uE64D',37740:'\uE64E',37741:'\uE64F',37742:'\uE650',37743:'\uE651',37744:'\uE652',37745:'\uE653',37746:'\uE654',37747:'\uE655',37748:'\uE656',37749:'\uE657',37750:'\uE658',37751:'\uE659',37752:'\uE65A',37753:'\uE65B',37754:'\uE65C',37755:'\uE65D',37756:'\uE65E',37757:'\uE65F',37758:'\uE660',37793:'\uE661',37794:'\uE662',37795:'\uE663',37796:'\uE664',37797:'\uE665',37798:'\uE666',37799:'\uE667',37800:'\uE668',37801:'\uE669',37802:'\uE66A',37803:'\uE66B',37804:'\uE66C',37805:'\uE66D',37806:'\uE66E',37807:'\uE66F',37808:'\uE670',37809:'\uE671',37810:'\uE672',37811:'\uE673',37812:'\uE674',37813:'\uE675',37814:'\uE676',37815:'\uE677',37816:'\uE678',37817:'\uE679',37818:'\uE67A',37819:'\uE67B',37820:'\uE67C',37821:'\uE67D',37822:'\uE67E',37823:'\uE67F',37824:'\uE680',37825:'\uE681',37826:'\uE682',37827:'\uE683',37828:'\uE684',37829:'\uE685',37830:'\uE686',37831:'\uE687',37832:'\uE688',37833:'\uE689',37834:'\uE68A',37835:'\uE68B',37836:'\uE68C',37837:'\uE68D',37838:'\uE68E',37839:'\uE68F',37840:'\uE690',37841:'\uE691',37842:'\uE692',37843:'\uE693',37844:'\uE694',37845:'\uE695',37846:'\uE696',37847:'\uE697',37848:'\uE698',37849:'\uE699',37850:'\uE69A',37851:'\uE69B',37852:'\uE69C',37853:'\uE69D',37854:'\uE69E',37855:'\uE69F',37856:'\uE6A0',37857:'\uE6A1',37858:'\uE6A2',37859:'\uE6A3',37860:'\uE6A4',37861:'\uE6A5',37862:'\uE6A6',37863:'\uE6A7',37864:'\uE6A8',37865:'\uE6A9',37866:'\uE6AA',37867:'\uE6AB',37868:'\uE6AC',37869:'\uE6AD',37870:'\uE6AE',37871:'\uE6AF',37872:'\uE6B0',37873:'\uE6B1',37874:'\uE6B2',37875:'\uE6B3',37876:'\uE6B4',37877:'\uE6B5',37878:'\uE6B6',37879:'\uE6B7',37880:'\uE6B8',37881:'\uE6B9',37882:'\uE6BA',37883:'\uE6BB',37884:'\uE6BC',37885:'\uE6BD',37886:'\uE6BE',37952:'\uE6BF',37953:'\uE6C0',37954:'\uE6C1',37955:'\uE6C2',37956:'\uE6C3',37957:'\uE6C4',37958:'\uE6C5',37959:'\uE6C6',37960:'\uE6C7',37961:'\uE6C8',37962:'\uE6C9',37963:'\uE6CA',37964:'\uE6CB',37965:'\uE6CC',37966:'\uE6CD',37967:'\uE6CE',37968:'\uE6CF',37969:'\uE6D0',37970:'\uE6D1',37971:'\uE6D2',37972:'\uE6D3',37973:'\uE6D4',37974:'\uE6D5',37975:'\uE6D6',37976:'\uE6D7',37977:'\uE6D8',37978:'\uE6D9',37979:'\uE6DA',37980:'\uE6DB',37981:'\uE6DC',37982:'\uE6DD',37983:'\uE6DE',37984:'\uE6DF',37985:'\uE6E0',37986:'\uE6E1',37987:'\uE6E2',37988:'\uE6E3',37989:'\uE6E4',37990:'\uE6E5',37991:'\uE6E6',37992:'\uE6E7',37993:'\uE6E8',37994:'\uE6E9',37995:'\uE6EA',37996:'\uE6EB',37997:'\uE6EC',37998:'\uE6ED',37999:'\uE6EE',38000:'\uE6EF',38001:'\uE6F0',38002:'\uE6F1',38003:'\uE6F2',38004:'\uE6F3',38005:'\uE6F4',38006:'\uE6F5',38007:'\uE6F6',38008:'\uE6F7',38009:'\uE6F8',38010:'\uE6F9',38011:'\uE6FA',38012:'\uE6FB',38013:'\uE6FC',38014:'\uE6FD',38049:'\uE6FE',38050:'\uE6FF',38051:'\uE700',38052:'\uE701',38053:'\uE702',38054:'\uE703',38055:'\uE704',38056:'\uE705',38057:'\uE706',38058:'\uE707',38059:'\uE708',38060:'\uE709',38061:'\uE70A',38062:'\uE70B',38063:'\uE70C',38064:'\uE70D',38065:'\uE70E',38066:'\uE70F',38067:'\uE710',38068:'\uE711',38069:'\uE712',38070:'\uE713',38071:'\uE714',38072:'\uE715',38073:'\uE716',38074:'\uE717',38075:'\uE718',38076:'\uE719',38077:'\uE71A',38078:'\uE71B',38079:'\uE71C',38080:'\uE71D',38081:'\uE71E',38082:'\uE71F',38083:'\uE720',38084:'\uE721',38085:'\uE722',38086:'\uE723',38087:'\uE724',38088:'\uE725',38089:'\uE726',38090:'\uE727',38091:'\uE728',38092:'\uE729',38093:'\uE72A',38094:'\uE72B',38095:'\uE72C',38096:'\uE72D',38097:'\uE72E',38098:'\uE72F',38099:'\uE730',38100:'\uE731',38101:'\uE732',38102:'\uE733',38103:'\uE734',38104:'\uE735',38105:'\uE736',38106:'\uE737',38107:'\uE738',38108:'\uE739',38109:'\uE73A',38110:'\uE73B',38111:'\uE73C',38112:'\uE73D',38113:'\uE73E',38114:'\uE73F',38115:'\uE740',38116:'\uE741',38117:'\uE742',38118:'\uE743',38119:'\uE744',38120:'\uE745',38121:'\uE746',38122:'\uE747',38123:'\uE748',38124:'\uE749',38125:'\uE74A',38126:'\uE74B',38127:'\uE74C',38128:'\uE74D',38129:'\uE74E',38130:'\uE74F',38131:'\uE750',38132:'\uE751',38133:'\uE752',38134:'\uE753',38135:'\uE754',38136:'\uE755',38137:'\uE756',38138:'\uE757',38139:'\uE758',38140:'\uE759',38141:'\uE75A',38142:'\uE75B',38208:'\uE75C',38209:'\uE75D',38210:'\uE75E',38211:'\uE75F',38212:'\uE760',38213:'\uE761',38214:'\uE762',38215:'\uE763',38216:'\uE764',38217:'\uE765',38218:'\uE766',38219:'\uE767',38220:'\uE768',38221:'\uE769',38222:'\uE76A',38223:'\uE76B',38224:'\uE76C',38225:'\uE76D',38226:'\uE76E',38227:'\uE76F',38228:'\uE770',38229:'\uE771',38230:'\uE772',38231:'\uE773',38232:'\uE774',38233:'\uE775',38234:'\uE776',38235:'\uE777',38236:'\uE778',38237:'\uE779',38238:'\uE77A',38239:'\uE77B',38240:'\uE77C',38241:'\uE77D',38242:'\uE77E',38243:'\uE77F',38244:'\uE780',38245:'\uE781',38246:'\uE782',38247:'\uE783',38248:'\uE784',38249:'\uE785',38250:'\uE786',38251:'\uE787',38252:'\uE788',38253:'\uE789',38254:'\uE78A',38255:'\uE78B',38256:'\uE78C',38257:'\uE78D',38258:'\uE78E',38259:'\uE78F',38260:'\uE790',38261:'\uE791',38262:'\uE792',38263:'\uE793',38264:'\uE794',38265:'\uE795',38266:'\uE796',38267:'\uE797',38268:'\uE798',38269:'\uE799',38270:'\uE79A',38305:'\uE79B',38306:'\uE79C',38307:'\uE79D',38308:'\uE79E',38309:'\uE79F',38310:'\uE7A0',38311:'\uE7A1',38312:'\uE7A2',38313:'\uE7A3',38314:'\uE7A4',38315:'\uE7A5',38316:'\uE7A6',38317:'\uE7A7',38318:'\uE7A8',38319:'\uE7A9',38320:'\uE7AA',38321:'\uE7AB',38322:'\uE7AC',38323:'\uE7AD',38324:'\uE7AE',38325:'\uE7AF',38326:'\uE7B0',38327:'\uE7B1',38328:'\uE7B2',38329:'\uE7B3',38330:'\uE7B4',38331:'\uE7B5',38332:'\uE7B6',38333:'\uE7B7',38334:'\uE7B8',38335:'\uE7B9',38336:'\uE7BA',38337:'\uE7BB',38338:'\uE7BC',38339:'\uE7BD',38340:'\uE7BE',38341:'\uE7BF',38342:'\uE7C0',38343:'\uE7C1',38344:'\uE7C2',38345:'\uE7C3',38346:'\uE7C4',38347:'\uE7C5',38348:'\uE7C6',38349:'\uE7C7',38350:'\uE7C8',38351:'\uE7C9',38352:'\uE7CA',38353:'\uE7CB',38354:'\uE7CC',38355:'\uE7CD',38356:'\uE7CE',38357:'\uE7CF',38358:'\uE7D0',38359:'\uE7D1',38360:'\uE7D2',38361:'\uE7D3',38362:'\uE7D4',38363:'\uE7D5',38364:'\uE7D6',38365:'\uE7D7',38366:'\uE7D8',38367:'\uE7D9',38368:'\uE7DA',38369:'\uE7DB',38370:'\uE7DC',38371:'\uE7DD',38372:'\uE7DE',38373:'\uE7DF',38374:'\uE7E0',38375:'\uE7E1',38376:'\uE7E2',38377:'\uE7E3',38378:'\uE7E4',38379:'\uE7E5',38380:'\uE7E6',38381:'\uE7E7',38382:'\uE7E8',38383:'\uE7E9',38384:'\uE7EA',38385:'\uE7EB',38386:'\uE7EC',38387:'\uE7ED',38388:'\uE7EE',38389:'\uE7EF',38390:'\uE7F0',38391:'\uE7F1',38392:'\uE7F2',38393:'\uE7F3',38394:'\uE7F4',38395:'\uE7F5',38396:'\uE7F6',38397:'\uE7F7',38398:'\uE7F8',38464:'\uE7F9',38465:'\uE7FA',38466:'\uE7FB',38467:'\uE7FC',38468:'\uE7FD',38469:'\uE7FE',38470:'\uE7FF',38471:'\uE800',38472:'\uE801',38473:'\uE802',38474:'\uE803',38475:'\uE804',38476:'\uE805',38477:'\uE806',38478:'\uE807',38479:'\uE808',38480:'\uE809',38481:'\uE80A',38482:'\uE80B',38483:'\uE80C',38484:'\uE80D',38485:'\uE80E',38486:'\uE80F',38487:'\uE810',38488:'\uE811',38489:'\uE812',38490:'\uE813',38491:'\uE814',38492:'\uE815',38493:'\uE816',38494:'\uE817',38495:'\uE818',38496:'\uE819',38497:'\uE81A',38498:'\uE81B',38499:'\uE81C',38500:'\uE81D',38501:'\uE81E',38502:'\uE81F',38503:'\uE820',38504:'\uE821',38505:'\uE822',38506:'\uE823',38507:'\uE824',38508:'\uE825',38509:'\uE826',38510:'\uE827',38511:'\uE828',38512:'\uE829',38513:'\uE82A',38514:'\uE82B',38515:'\uE82C',38516:'\uE82D',38517:'\uE82E',38518:'\uE82F',38519:'\uE830',38520:'\uE831',38521:'\uE832',38522:'\uE833',38523:'\uE834',38524:'\uE835',38525:'\uE836',38526:'\uE837',38561:'\uE838',38562:'\uE839',38563:'\uE83A',38564:'\uE83B',38565:'\uE83C',38566:'\uE83D',38567:'\uE83E',38568:'\uE83F',38569:'\uE840',38570:'\uE841',38571:'\uE842',38572:'\uE843',38573:'\uE844',38574:'\uE845',38575:'\uE846',38576:'\uE847',38577:'\uE848',38578:'\uE849',38579:'\uE84A',38580:'\uE84B',38581:'\uE84C',38582:'\uE84D',38583:'\uE84E',38584:'\uE84F',38585:'\uE850',38586:'\uE851',38587:'\uE852',38588:'\uE853',38589:'\uE854',38590:'\uE855',38591:'\uE856',38592:'\uE857',38593:'\uE858',38594:'\uE859',38595:'\uE85A',38596:'\uE85B',38597:'\uE85C',38598:'\uE85D',38599:'\uE85E',38600:'\uE85F',38601:'\uE860',38602:'\uE861',38603:'\uE862',38604:'\uE863',38605:'\uE864',38606:'\uE865',38607:'\uE866',38608:'\uE867',38609:'\uE868',38610:'\uE869',38611:'\uE86A',38612:'\uE86B',38613:'\uE86C',38614:'\uE86D',38615:'\uE86E',38616:'\uE86F',38617:'\uE870',38618:'\uE871',38619:'\uE872',38620:'\uE873',38621:'\uE874',38622:'\uE875',38623:'\uE876',38624:'\uE877',38625:'\uE878',38626:'\uE879',38627:'\uE87A',38628:'\uE87B',38629:'\uE87C',38630:'\uE87D',38631:'\uE87E',38632:'\uE87F',38633:'\uE880',38634:'\uE881',38635:'\uE882',38636:'\uE883',38637:'\uE884',38638:'\uE885',38639:'\uE886',38640:'\uE887',38641:'\uE888',38642:'\uE889',38643:'\uE88A',38644:'\uE88B',38645:'\uE88C',38646:'\uE88D',38647:'\uE88E',38648:'\uE88F',38649:'\uE890',38650:'\uE891',38651:'\uE892',38652:'\uE893',38653:'\uE894',38654:'\uE895',38720:'\uE896',38721:'\uE897',38722:'\uE898',38723:'\uE899',38724:'\uE89A',38725:'\uE89B',38726:'\uE89C',38727:'\uE89D',38728:'\uE89E',38729:'\uE89F',38730:'\uE8A0',38731:'\uE8A1',38732:'\uE8A2',38733:'\uE8A3',38734:'\uE8A4',38735:'\uE8A5',38736:'\uE8A6',38737:'\uE8A7',38738:'\uE8A8',38739:'\uE8A9',38740:'\uE8AA',38741:'\uE8AB',38742:'\uE8AC',38743:'\uE8AD',38744:'\uE8AE',38745:'\uE8AF',38746:'\uE8B0',38747:'\uE8B1',38748:'\uE8B2',38749:'\uE8B3',38750:'\uE8B4',38751:'\uE8B5',38752:'\uE8B6',38753:'\uE8B7',38754:'\uE8B8',38755:'\uE8B9',38756:'\uE8BA',38757:'\uE8BB',38758:'\uE8BC',38759:'\uE8BD',38760:'\uE8BE',38761:'\uE8BF',38762:'\uE8C0',38763:'\uE8C1',38764:'\uE8C2',38765:'\uE8C3',38766:'\uE8C4',38767:'\uE8C5',38768:'\uE8C6',38769:'\uE8C7',38770:'\uE8C8',38771:'\uE8C9',38772:'\uE8CA',38773:'\uE8CB',38774:'\uE8CC',38775:'\uE8CD',38776:'\uE8CE',38777:'\uE8CF',38778:'\uE8D0',38779:'\uE8D1',38780:'\uE8D2',38781:'\uE8D3',38782:'\uE8D4',38817:'\uE8D5',38818:'\uE8D6',38819:'\uE8D7',38820:'\uE8D8',38821:'\uE8D9',38822:'\uE8DA',38823:'\uE8DB',38824:'\uE8DC',38825:'\uE8DD',38826:'\uE8DE',38827:'\uE8DF',38828:'\uE8E0',38829:'\uE8E1',38830:'\uE8E2',38831:'\uE8E3',38832:'\uE8E4',38833:'\uE8E5',38834:'\uE8E6',38835:'\uE8E7',38836:'\uE8E8',38837:'\uE8E9',38838:'\uE8EA',38839:'\uE8EB',38840:'\uE8EC',38841:'\uE8ED',38842:'\uE8EE',38843:'\uE8EF',38844:'\uE8F0',38845:'\uE8F1',38846:'\uE8F2',38847:'\uE8F3',38848:'\uE8F4',38849:'\uE8F5',38850:'\uE8F6',38851:'\uE8F7',38852:'\uE8F8',38853:'\uE8F9',38854:'\uE8FA',38855:'\uE8FB',38856:'\uE8FC',38857:'\uE8FD',38858:'\uE8FE',38859:'\uE8FF',38860:'\uE900',38861:'\uE901',38862:'\uE902',38863:'\uE903',38864:'\uE904',38865:'\uE905',38866:'\uE906',38867:'\uE907',38868:'\uE908',38869:'\uE909',38870:'\uE90A',38871:'\uE90B',38872:'\uE90C',38873:'\uE90D',38874:'\uE90E',38875:'\uE90F',38876:'\uE910',38877:'\uE911',38878:'\uE912',38879:'\uE913',38880:'\uE914',38881:'\uE915',38882:'\uE916',38883:'\uE917',38884:'\uE918',38885:'\uE919',38886:'\uE91A',38887:'\uE91B',38888:'\uE91C',38889:'\uE91D',38890:'\uE91E',38891:'\uE91F',38892:'\uE920',38893:'\uE921',38894:'\uE922',38895:'\uE923',38896:'\uE924',38897:'\uE925',38898:'\uE926',38899:'\uE927',38900:'\uE928',38901:'\uE929',38902:'\uE92A',38903:'\uE92B',38904:'\uE92C',38905:'\uE92D',38906:'\uE92E',38907:'\uE92F',38908:'\uE930',38909:'\uE931',38910:'\uE932',38976:'\uE933',38977:'\uE934',38978:'\uE935',38979:'\uE936',38980:'\uE937',38981:'\uE938',38982:'\uE939',38983:'\uE93A',38984:'\uE93B',38985:'\uE93C',38986:'\uE93D',38987:'\uE93E',38988:'\uE93F',38989:'\uE940',38990:'\uE941',38991:'\uE942',38992:'\uE943',38993:'\uE944',38994:'\uE945',38995:'\uE946',38996:'\uE947',38997:'\uE948',38998:'\uE949',38999:'\uE94A',39000:'\uE94B',39001:'\uE94C',39002:'\uE94D',39003:'\uE94E',39004:'\uE94F',39005:'\uE950',39006:'\uE951',39007:'\uE952',39008:'\uE953',39009:'\uE954',39010:'\uE955',39011:'\uE956',39012:'\uE957',39013:'\uE958',39014:'\uE959',39015:'\uE95A',39016:'\uE95B',39017:'\uE95C',39018:'\uE95D',39019:'\uE95E',39020:'\uE95F',39021:'\uE960',39022:'\uE961',39023:'\uE962',39024:'\uE963',39025:'\uE964',39026:'\uE965',39027:'\uE966',39028:'\uE967',39029:'\uE968',39030:'\uE969',39031:'\uE96A',39032:'\uE96B',39033:'\uE96C',39034:'\uE96D',39035:'\uE96E',39036:'\uE96F',39037:'\uE970',39038:'\uE971',39073:'\uE972',39074:'\uE973',39075:'\uE974',39076:'\uE975',39077:'\uE976',39078:'\uE977',39079:'\uE978',39080:'\uE979',39081:'\uE97A',39082:'\uE97B',39083:'\uE97C',39084:'\uE97D',39085:'\uE97E',39086:'\uE97F',39087:'\uE980',39088:'\uE981',39089:'\uE982',39090:'\uE983',39091:'\uE984',39092:'\uE985',39093:'\uE986',39094:'\uE987',39095:'\uE988',39096:'\uE989',39097:'\uE98A',39098:'\uE98B',39099:'\uE98C',39100:'\uE98D',39101:'\uE98E',39102:'\uE98F',39103:'\uE990',39104:'\uE991',39105:'\uE992',39106:'\uE993',39107:'\uE994',39108:'\uE995',39109:'\uE996',39110:'\uE997',39111:'\uE998',39112:'\uE999',39113:'\uE99A',39114:'\uE99B',39115:'\uE99C',39116:'\uE99D',39117:'\uE99E',39118:'\uE99F',39119:'\uE9A0',39120:'\uE9A1',39121:'\uE9A2',39122:'\uE9A3',39123:'\uE9A4',39124:'\uE9A5',39125:'\uE9A6',39126:'\uE9A7',39127:'\uE9A8',39128:'\uE9A9',39129:'\uE9AA',39130:'\uE9AB',39131:'\uE9AC',39132:'\uE9AD',39133:'\uE9AE',39134:'\uE9AF',39135:'\uE9B0',39136:'\uE9B1',39137:'\uE9B2',39138:'\uE9B3',39139:'\uE9B4',39140:'\uE9B5',39141:'\uE9B6',39142:'\uE9B7',39143:'\uE9B8',39144:'\uE9B9',39145:'\uE9BA',39146:'\uE9BB',39147:'\uE9BC',39148:'\uE9BD',39149:'\uE9BE',39150:'\uE9BF',39151:'\uE9C0',39152:'\uE9C1',39153:'\uE9C2',39154:'\uE9C3',39155:'\uE9C4',39156:'\uE9C5',39157:'\uE9C6',39158:'\uE9C7',39159:'\uE9C8',39160:'\uE9C9',39161:'\uE9CA',39162:'\uE9CB',39163:'\uE9CC',39164:'\uE9CD',39165:'\uE9CE',39166:'\uE9CF',39232:'\uE9D0',39233:'\uE9D1',39234:'\uE9D2',39235:'\uE9D3',39236:'\uE9D4',39237:'\uE9D5',39238:'\uE9D6',39239:'\uE9D7',39240:'\uE9D8',39241:'\uE9D9',39242:'\uE9DA',39243:'\uE9DB',39244:'\uE9DC',39245:'\uE9DD',39246:'\uE9DE',39247:'\uE9DF',39248:'\uE9E0',39249:'\uE9E1',39250:'\uE9E2',39251:'\uE9E3',39252:'\uE9E4',39253:'\uE9E5',39254:'\uE9E6',39255:'\uE9E7',39256:'\uE9E8',39257:'\uE9E9',39258:'\uE9EA',39259:'\uE9EB',39260:'\uE9EC',39261:'\uE9ED',39262:'\uE9EE',39263:'\uE9EF',39264:'\uE9F0',39265:'\uE9F1',39266:'\uE9F2',39267:'\uE9F3',39268:'\uE9F4',39269:'\uE9F5',39270:'\uE9F6',39271:'\uE9F7',39272:'\uE9F8',39273:'\uE9F9',39274:'\uE9FA',39275:'\uE9FB',39276:'\uE9FC',39277:'\uE9FD',39278:'\uE9FE',39279:'\uE9FF',39280:'\uEA00',39281:'\uEA01',39282:'\uEA02',39283:'\uEA03',39284:'\uEA04',39285:'\uEA05',39286:'\uEA06',39287:'\uEA07',39288:'\uEA08',39289:'\uEA09',39290:'\uEA0A',39291:'\uEA0B',39292:'\uEA0C',39293:'\uEA0D',39294:'\uEA0E',39329:'\uEA0F',39330:'\uEA10',39331:'\uEA11',39332:'\uEA12',39333:'\uEA13',39334:'\uEA14',39335:'\uEA15',39336:'\uEA16',39337:'\uEA17',39338:'\uEA18',39339:'\uEA19',39340:'\uEA1A',39341:'\uEA1B',39342:'\uEA1C',39343:'\uEA1D',39344:'\uEA1E',39345:'\uEA1F',39346:'\uEA20',39347:'\uEA21',39348:'\uEA22',39349:'\uEA23',39350:'\uEA24',39351:'\uEA25',39352:'\uEA26',39353:'\uEA27',39354:'\uEA28',39355:'\uEA29',39356:'\uEA2A',39357:'\uEA2B',39358:'\uEA2C',39359:'\uEA2D',39360:'\uEA2E',39361:'\uEA2F',39362:'\uEA30',39363:'\uEA31',39364:'\uEA32',39365:'\uEA33',39366:'\uEA34',39367:'\uEA35',39368:'\uEA36',39369:'\uEA37',39370:'\uEA38',39371:'\uEA39',39372:'\uEA3A',39373:'\uEA3B',39374:'\uEA3C',39375:'\uEA3D',39376:'\uEA3E',39377:'\uEA3F',39378:'\uEA40',39379:'\uEA41',39380:'\uEA42',39381:'\uEA43',39382:'\uEA44',39383:'\uEA45',39384:'\uEA46',39385:'\uEA47',39386:'\uEA48',39387:'\uEA49',39388:'\uEA4A',39389:'\uEA4B',39390:'\uEA4C',39391:'\uEA4D',39392:'\uEA4E',39393:'\uEA4F',39394:'\uEA50',39395:'\uEA51',39396:'\uEA52',39397:'\uEA53',39398:'\uEA54',39399:'\uEA55',39400:'\uEA56',39401:'\uEA57',39402:'\uEA58',39403:'\uEA59',39404:'\uEA5A',39405:'\uEA5B',39406:'\uEA5C',39407:'\uEA5D',39408:'\uEA5E',39409:'\uEA5F',39410:'\uEA60',39411:'\uEA61',39412:'\uEA62',39413:'\uEA63',39414:'\uEA64',39415:'\uEA65',39416:'\uEA66',39417:'\uEA67',39418:'\uEA68',39419:'\uEA69',39420:'\uEA6A',39421:'\uEA6B',39422:'\uEA6C',39488:'\uEA6D',39489:'\uEA6E',39490:'\uEA6F',39491:'\uEA70',39492:'\uEA71',39493:'\uEA72',39494:'\uEA73',39495:'\uEA74',39496:'\uEA75',39497:'\uEA76',39498:'\uEA77',39499:'\uEA78',39500:'\uEA79',39501:'\uEA7A',39502:'\uEA7B',39503:'\uEA7C',39504:'\uEA7D',39505:'\uEA7E',39506:'\uEA7F',39507:'\uEA80',39508:'\uEA81',39509:'\uEA82',39510:'\uEA83',39511:'\uEA84',39512:'\uEA85',39513:'\uEA86',39514:'\uEA87',39515:'\uEA88',39516:'\uEA89',39517:'\uEA8A',39518:'\uEA8B',39519:'\uEA8C',39520:'\uEA8D',39521:'\uEA8E',39522:'\uEA8F',39523:'\uEA90',39524:'\uEA91',39525:'\uEA92',39526:'\uEA93',39527:'\uEA94',39528:'\uEA95',39529:'\uEA96',39530:'\uEA97',39531:'\uEA98',39532:'\uEA99',39533:'\uEA9A',39534:'\uEA9B',39535:'\uEA9C',39536:'\uEA9D',39537:'\uEA9E',39538:'\uEA9F',39539:'\uEAA0',39540:'\uEAA1',39541:'\uEAA2',39542:'\uEAA3',39543:'\uEAA4',39544:'\uEAA5',39545:'\uEAA6',39546:'\uEAA7',39547:'\uEAA8',39548:'\uEAA9',39549:'\uEAAA',39550:'\uEAAB',39585:'\uEAAC',39586:'\uEAAD',39587:'\uEAAE',39588:'\uEAAF',39589:'\uEAB0',39590:'\uEAB1',39591:'\uEAB2',39592:'\uEAB3',39593:'\uEAB4',39594:'\uEAB5',39595:'\uEAB6',39596:'\uEAB7',39597:'\uEAB8',39598:'\uEAB9',39599:'\uEABA',39600:'\uEABB',39601:'\uEABC',39602:'\uEABD',39603:'\uEABE',39604:'\uEABF',39605:'\uEAC0',39606:'\uEAC1',39607:'\uEAC2',39608:'\uEAC3',39609:'\uEAC4',39610:'\uEAC5',39611:'\uEAC6',39612:'\uEAC7',39613:'\uEAC8',39614:'\uEAC9',39615:'\uEACA',39616:'\uEACB',39617:'\uEACC',39618:'\uEACD',39619:'\uEACE',39620:'\uEACF',39621:'\uEAD0',39622:'\uEAD1',39623:'\uEAD2',39624:'\uEAD3',39625:'\uEAD4',39626:'\uEAD5',39627:'\uEAD6',39628:'\uEAD7',39629:'\uEAD8',39630:'\uEAD9',39631:'\uEADA',39632:'\uEADB',39633:'\uEADC',39634:'\uEADD',39635:'\uEADE',39636:'\uEADF',39637:'\uEAE0',39638:'\uEAE1',39639:'\uEAE2',39640:'\uEAE3',39641:'\uEAE4',39642:'\uEAE5',39643:'\uEAE6',39644:'\uEAE7',39645:'\uEAE8',39646:'\uEAE9',39647:'\uEAEA',39648:'\uEAEB',39649:'\uEAEC',39650:'\uEAED',39651:'\uEAEE',39652:'\uEAEF',39653:'\uEAF0',39654:'\uEAF1',39655:'\uEAF2',39656:'\uEAF3',39657:'\uEAF4',39658:'\uEAF5',39659:'\uEAF6',39660:'\uEAF7',39661:'\uEAF8',39662:'\uEAF9',39663:'\uEAFA',39664:'\uEAFB',39665:'\uEAFC',39666:'\uEAFD',39667:'\uEAFE',39668:'\uEAFF',39669:'\uEB00',39670:'\uEB01',39671:'\uEB02',39672:'\uEB03',39673:'\uEB04',39674:'\uEB05',39675:'\uEB06',39676:'\uEB07',39677:'\uEB08',39678:'\uEB09',39744:'\uEB0A',39745:'\uEB0B',39746:'\uEB0C',39747:'\uEB0D',39748:'\uEB0E',39749:'\uEB0F',39750:'\uEB10',39751:'\uEB11',39752:'\uEB12',39753:'\uEB13',39754:'\uEB14',39755:'\uEB15',39756:'\uEB16',39757:'\uEB17',39758:'\uEB18',39759:'\uEB19',39760:'\uEB1A',39761:'\uEB1B',39762:'\uEB1C',39763:'\uEB1D',39764:'\uEB1E',39765:'\uEB1F',39766:'\uEB20',39767:'\uEB21',39768:'\uEB22',39769:'\uEB23',39770:'\uEB24',39771:'\uEB25',39772:'\uEB26',39773:'\uEB27',39774:'\uEB28',39775:'\uEB29',39776:'\uEB2A',39777:'\uEB2B',39778:'\uEB2C',39779:'\uEB2D',39780:'\uEB2E',39781:'\uEB2F',39782:'\uEB30',39783:'\uEB31',39784:'\uEB32',39785:'\uEB33',39786:'\uEB34',39787:'\uEB35',39788:'\uEB36',39789:'\uEB37',39790:'\uEB38',39791:'\uEB39',39792:'\uEB3A',39793:'\uEB3B',39794:'\uEB3C',39795:'\uEB3D',39796:'\uEB3E',39797:'\uEB3F',39798:'\uEB40',39799:'\uEB41',39800:'\uEB42',39801:'\uEB43',39802:'\uEB44',39803:'\uEB45',39804:'\uEB46',39805:'\uEB47',39806:'\uEB48',39841:'\uEB49',39842:'\uEB4A',39843:'\uEB4B',39844:'\uEB4C',39845:'\uEB4D',39846:'\uEB4E',39847:'\uEB4F',39848:'\uEB50',39849:'\uEB51',39850:'\uEB52',39851:'\uEB53',39852:'\uEB54',39853:'\uEB55',39854:'\uEB56',39855:'\uEB57',39856:'\uEB58',39857:'\uEB59',39858:'\uEB5A',39859:'\uEB5B',39860:'\uEB5C',39861:'\uEB5D',39862:'\uEB5E',39863:'\uEB5F',39864:'\uEB60',39865:'\uEB61',39866:'\uEB62',39867:'\uEB63',39868:'\uEB64',39869:'\uEB65',39870:'\uEB66',39871:'\uEB67',39872:'\uEB68',39873:'\uEB69',39874:'\uEB6A',39875:'\uEB6B',39876:'\uEB6C',39877:'\uEB6D',39878:'\uEB6E',39879:'\uEB6F',39880:'\uEB70',39881:'\uEB71',39882:'\uEB72',39883:'\uEB73',39884:'\uEB74',39885:'\uEB75',39886:'\uEB76',39887:'\uEB77',39888:'\uEB78',39889:'\uEB79',39890:'\uEB7A',39891:'\uEB7B',39892:'\uEB7C',39893:'\uEB7D',39894:'\uEB7E',39895:'\uEB7F',39896:'\uEB80',39897:'\uEB81',39898:'\uEB82',39899:'\uEB83',39900:'\uEB84',39901:'\uEB85',39902:'\uEB86',39903:'\uEB87',39904:'\uEB88',39905:'\uEB89',39906:'\uEB8A',39907:'\uEB8B',39908:'\uEB8C',39909:'\uEB8D',39910:'\uEB8E',39911:'\uEB8F',39912:'\uEB90',39913:'\uEB91',39914:'\uEB92',39915:'\uEB93',39916:'\uEB94',39917:'\uEB95',39918:'\uEB96',39919:'\uEB97',39920:'\uEB98',39921:'\uEB99',39922:'\uEB9A',39923:'\uEB9B',39924:'\uEB9C',39925:'\uEB9D',39926:'\uEB9E',39927:'\uEB9F',39928:'\uEBA0',39929:'\uEBA1',39930:'\uEBA2',39931:'\uEBA3',39932:'\uEBA4',39933:'\uEBA5',39934:'\uEBA6',40000:'\uEBA7',40001:'\uEBA8',40002:'\uEBA9',40003:'\uEBAA',40004:'\uEBAB',40005:'\uEBAC',40006:'\uEBAD',40007:'\uEBAE',40008:'\uEBAF',40009:'\uEBB0',40010:'\uEBB1',40011:'\uEBB2',40012:'\uEBB3',40013:'\uEBB4',40014:'\uEBB5',40015:'\uEBB6',40016:'\uEBB7',40017:'\uEBB8',40018:'\uEBB9',40019:'\uEBBA',40020:'\uEBBB',40021:'\uEBBC',40022:'\uEBBD',40023:'\uEBBE',40024:'\uEBBF',40025:'\uEBC0',40026:'\uEBC1',40027:'\uEBC2',40028:'\uEBC3',40029:'\uEBC4',40030:'\uEBC5',40031:'\uEBC6',40032:'\uEBC7',40033:'\uEBC8',40034:'\uEBC9',40035:'\uEBCA',40036:'\uEBCB',40037:'\uEBCC',40038:'\uEBCD',40039:'\uEBCE',40040:'\uEBCF',40041:'\uEBD0',40042:'\uEBD1',40043:'\uEBD2',40044:'\uEBD3',40045:'\uEBD4',40046:'\uEBD5',40047:'\uEBD6',40048:'\uEBD7',40049:'\uEBD8',40050:'\uEBD9',40051:'\uEBDA',40052:'\uEBDB',40053:'\uEBDC',40054:'\uEBDD',40055:'\uEBDE',40056:'\uEBDF',40057:'\uEBE0',40058:'\uEBE1',40059:'\uEBE2',40060:'\uEBE3',40061:'\uEBE4',40062:'\uEBE5',40097:'\uEBE6',40098:'\uEBE7',40099:'\uEBE8',40100:'\uEBE9',40101:'\uEBEA',40102:'\uEBEB',40103:'\uEBEC',40104:'\uEBED',40105:'\uEBEE',40106:'\uEBEF',40107:'\uEBF0',40108:'\uEBF1',40109:'\uEBF2',40110:'\uEBF3',40111:'\uEBF4',40112:'\uEBF5',40113:'\uEBF6',40114:'\uEBF7',40115:'\uEBF8',40116:'\uEBF9',40117:'\uEBFA',40118:'\uEBFB',40119:'\uEBFC',40120:'\uEBFD',40121:'\uEBFE',40122:'\uEBFF',40123:'\uEC00',40124:'\uEC01',40125:'\uEC02',40126:'\uEC03',40127:'\uEC04',40128:'\uEC05',40129:'\uEC06',40130:'\uEC07',40131:'\uEC08',40132:'\uEC09',40133:'\uEC0A',40134:'\uEC0B',40135:'\uEC0C',40136:'\uEC0D',40137:'\uEC0E',40138:'\uEC0F',40139:'\uEC10',40140:'\uEC11',40141:'\uEC12',40142:'\uEC13',40143:'\uEC14',40144:'\uEC15',40145:'\uEC16',40146:'\uEC17',40147:'\uEC18',40148:'\uEC19',40149:'\uEC1A',40150:'\uEC1B',40151:'\uEC1C',40152:'\uEC1D',40153:'\uEC1E',40154:'\uEC1F',40155:'\uEC20',40156:'\uEC21',40157:'\uEC22',40158:'\uEC23',40159:'\uEC24',40160:'\uEC25',40161:'\uEC26',40162:'\uEC27',40163:'\uEC28',40164:'\uEC29',40165:'\uEC2A',40166:'\uEC2B',40167:'\uEC2C',40168:'\uEC2D',40169:'\uEC2E',40170:'\uEC2F',40171:'\uEC30',40172:'\uEC31',40173:'\uEC32',40174:'\uEC33',40175:'\uEC34',40176:'\uEC35',40177:'\uEC36',40178:'\uEC37',40179:'\uEC38',40180:'\uEC39',40181:'\uEC3A',40182:'\uEC3B',40183:'\uEC3C',40184:'\uEC3D',40185:'\uEC3E',40186:'\uEC3F',40187:'\uEC40',40188:'\uEC41',40189:'\uEC42',40190:'\uEC43',40256:'\uEC44',40257:'\uEC45',40258:'\uEC46',40259:'\uEC47',40260:'\uEC48',40261:'\uEC49',40262:'\uEC4A',40263:'\uEC4B',40264:'\uEC4C',40265:'\uEC4D',40266:'\uEC4E',40267:'\uEC4F',40268:'\uEC50',40269:'\uEC51',40270:'\uEC52',40271:'\uEC53',40272:'\uEC54',40273:'\uEC55',40274:'\uEC56',40275:'\uEC57',40276:'\uEC58',40277:'\uEC59',40278:'\uEC5A',40279:'\uEC5B',40280:'\uEC5C',40281:'\uEC5D',40282:'\uEC5E',40283:'\uEC5F',40284:'\uEC60',40285:'\uEC61',40286:'\uEC62',40287:'\uEC63',40288:'\uEC64',40289:'\uEC65',40290:'\uEC66',40291:'\uEC67',40292:'\uEC68',40293:'\uEC69',40294:'\uEC6A',40295:'\uEC6B',40296:'\uEC6C',40297:'\uEC6D',40298:'\uEC6E',40299:'\uEC6F',40300:'\uEC70',40301:'\uEC71',40302:'\uEC72',40303:'\uEC73',40304:'\uEC74',40305:'\uEC75',40306:'\uEC76',40307:'\uEC77',40308:'\uEC78',40309:'\uEC79',40310:'\uEC7A',40311:'\uEC7B',40312:'\uEC7C',40313:'\uEC7D',40314:'\uEC7E',40315:'\uEC7F',40316:'\uEC80',40317:'\uEC81',40318:'\uEC82',40353:'\uEC83',40354:'\uEC84',40355:'\uEC85',40356:'\uEC86',40357:'\uEC87',40358:'\uEC88',40359:'\uEC89',40360:'\uEC8A',40361:'\uEC8B',40362:'\uEC8C',40363:'\uEC8D',40364:'\uEC8E',40365:'\uEC8F',40366:'\uEC90',40367:'\uEC91',40368:'\uEC92',40369:'\uEC93',40370:'\uEC94',40371:'\uEC95',40372:'\uEC96',40373:'\uEC97',40374:'\uEC98',40375:'\uEC99',40376:'\uEC9A',40377:'\uEC9B',40378:'\uEC9C',40379:'\uEC9D',40380:'\uEC9E',40381:'\uEC9F',40382:'\uECA0',40383:'\uECA1',40384:'\uECA2',40385:'\uECA3',40386:'\uECA4',40387:'\uECA5',40388:'\uECA6',40389:'\uECA7',40390:'\uECA8',40391:'\uECA9',40392:'\uECAA',40393:'\uECAB',40394:'\uECAC',40395:'\uECAD',40396:'\uECAE',40397:'\uECAF',40398:'\uECB0',40399:'\uECB1',40400:'\uECB2',40401:'\uECB3',40402:'\uECB4',40403:'\uECB5',40404:'\uECB6',40405:'\uECB7',40406:'\uECB8',40407:'\uECB9',40408:'\uECBA',40409:'\uECBB',40410:'\uECBC',40411:'\uECBD',40412:'\uECBE',40413:'\uECBF',40414:'\uECC0',40415:'\uECC1',40416:'\uECC2',40417:'\uECC3',40418:'\uECC4',40419:'\uECC5',40420:'\uECC6',40421:'\uECC7',40422:'\uECC8',40423:'\uECC9',40424:'\uECCA',40425:'\uECCB',40426:'\uECCC',40427:'\uECCD',40428:'\uECCE',40429:'\uECCF',40430:'\uECD0',40431:'\uECD1',40432:'\uECD2',40433:'\uECD3',40434:'\uECD4',40435:'\uECD5',40436:'\uECD6',40437:'\uECD7',40438:'\uECD8',40439:'\uECD9',40440:'\uECDA',40441:'\uECDB',40442:'\uECDC',40443:'\uECDD',40444:'\uECDE',40445:'\uECDF',40446:'\uECE0',40512:'\uECE1',40513:'\uECE2',40514:'\uECE3',40515:'\uECE4',40516:'\uECE5',40517:'\uECE6',40518:'\uECE7',40519:'\uECE8',40520:'\uECE9',40521:'\uECEA',40522:'\uECEB',40523:'\uECEC',40524:'\uECED',40525:'\uECEE',40526:'\uECEF',40527:'\uECF0',40528:'\uECF1',40529:'\uECF2',40530:'\uECF3',40531:'\uECF4',40532:'\uECF5',40533:'\uECF6',40534:'\uECF7',40535:'\uECF8',40536:'\uECF9',40537:'\uECFA',40538:'\uECFB',40539:'\uECFC',40540:'\uECFD',40541:'\uECFE',40542:'\uECFF',40543:'\uED00',40544:'\uED01',40545:'\uED02',40546:'\uED03',40547:'\uED04',40548:'\uED05',40549:'\uED06',40550:'\uED07',40551:'\uED08',40552:'\uED09',40553:'\uED0A',40554:'\uED0B',40555:'\uED0C',40556:'\uED0D',40557:'\uED0E',40558:'\uED0F',40559:'\uED10',40560:'\uED11',40561:'\uED12',40562:'\uED13',40563:'\uED14',40564:'\uED15',40565:'\uED16',40566:'\uED17',40567:'\uED18',40568:'\uED19',40569:'\uED1A',40570:'\uED1B',40571:'\uED1C',40572:'\uED1D',40573:'\uED1E',40574:'\uED1F',40609:'\uED20',40610:'\uED21',40611:'\uED22',40612:'\uED23',40613:'\uED24',40614:'\uED25',40615:'\uED26',40616:'\uED27',40617:'\uED28',40618:'\uED29',40619:'\uED2A',40620:'\uED2B',40621:'\uED2C',40622:'\uED2D',40623:'\uED2E',40624:'\uED2F',40625:'\uED30',40626:'\uED31',40627:'\uED32',40628:'\uED33',40629:'\uED34',40630:'\uED35',40631:'\uED36',40632:'\uED37',40633:'\uED38',40634:'\uED39',40635:'\uED3A',40636:'\uED3B',40637:'\uED3C',40638:'\uED3D',40639:'\uED3E',40640:'\uED3F',40641:'\uED40',40642:'\uED41',40643:'\uED42',40644:'\uED43',40645:'\uED44',40646:'\uED45',40647:'\uED46',40648:'\uED47',40649:'\uED48',40650:'\uED49',40651:'\uED4A',40652:'\uED4B',40653:'\uED4C',40654:'\uED4D',40655:'\uED4E',40656:'\uED4F',40657:'\uED50',40658:'\uED51',40659:'\uED52',40660:'\uED53',40661:'\uED54',40662:'\uED55',40663:'\uED56',40664:'\uED57',40665:'\uED58',40666:'\uED59',40667:'\uED5A',40668:'\uED5B',40669:'\uED5C',40670:'\uED5D',40671:'\uED5E',40672:'\uED5F',40673:'\uED60',40674:'\uED61',40675:'\uED62',40676:'\uED63',40677:'\uED64',40678:'\uED65',40679:'\uED66',40680:'\uED67',40681:'\uED68',40682:'\uED69',40683:'\uED6A',40684:'\uED6B',40685:'\uED6C',40686:'\uED6D',40687:'\uED6E',40688:'\uED6F',40689:'\uED70',40690:'\uED71',40691:'\uED72',40692:'\uED73',40693:'\uED74',40694:'\uED75',40695:'\uED76',40696:'\uED77',40697:'\uED78',40698:'\uED79',40699:'\uED7A',40700:'\uED7B',40701:'\uED7C',40702:'\uED7D',40768:'\uED7E',40769:'\uED7F',40770:'\uED80',40771:'\uED81',40772:'\uED82',40773:'\uED83',40774:'\uED84',40775:'\uED85',40776:'\uED86',40777:'\uED87',40778:'\uED88',40779:'\uED89',40780:'\uED8A',40781:'\uED8B',40782:'\uED8C',40783:'\uED8D',40784:'\uED8E',40785:'\uED8F',40786:'\uED90',40787:'\uED91',40788:'\uED92',40789:'\uED93',40790:'\uED94',40791:'\uED95',40792:'\uED96',40793:'\uED97',40794:'\uED98',40795:'\uED99',40796:'\uED9A',40797:'\uED9B',40798:'\uED9C',40799:'\uED9D',40800:'\uED9E',40801:'\uED9F',40802:'\uEDA0',40803:'\uEDA1',40804:'\uEDA2',40805:'\uEDA3',40806:'\uEDA4',40807:'\uEDA5',40808:'\uEDA6',40809:'\uEDA7',40810:'\uEDA8',40811:'\uEDA9',40812:'\uEDAA',40813:'\uEDAB',40814:'\uEDAC',40815:'\uEDAD',40816:'\uEDAE',40817:'\uEDAF',40818:'\uEDB0',40819:'\uEDB1',40820:'\uEDB2',40821:'\uEDB3',40822:'\uEDB4',40823:'\uEDB5',40824:'\uEDB6',40825:'\uEDB7',40826:'\uEDB8',40827:'\uEDB9',40828:'\uEDBA',40829:'\uEDBB',40830:'\uEDBC',40865:'\uEDBD',40866:'\uEDBE',40867:'\uEDBF',40868:'\uEDC0',40869:'\uEDC1',40870:'\uEDC2',40871:'\uEDC3',40872:'\uEDC4',40873:'\uEDC5',40874:'\uEDC6',40875:'\uEDC7',40876:'\uEDC8',40877:'\uEDC9',40878:'\uEDCA',40879:'\uEDCB',40880:'\uEDCC',40881:'\uEDCD',40882:'\uEDCE',40883:'\uEDCF',40884:'\uEDD0',40885:'\uEDD1',40886:'\uEDD2',40887:'\uEDD3',40888:'\uEDD4',40889:'\uEDD5',40890:'\uEDD6',40891:'\uEDD7',40892:'\uEDD8',40893:'\uEDD9',40894:'\uEDDA',40895:'\uEDDB',40896:'\uEDDC',40897:'\uEDDD',40898:'\uEDDE',40899:'\uEDDF',40900:'\uEDE0',40901:'\uEDE1',40902:'\uEDE2',40903:'\uEDE3',40904:'\uEDE4',40905:'\uEDE5',40906:'\uEDE6',40907:'\uEDE7',40908:'\uEDE8',40909:'\uEDE9',40910:'\uEDEA',40911:'\uEDEB',40912:'\uEDEC',40913:'\uEDED',40914:'\uEDEE',40915:'\uEDEF',40916:'\uEDF0',40917:'\uEDF1',40918:'\uEDF2',40919:'\uEDF3',40920:'\uEDF4',40921:'\uEDF5',40922:'\uEDF6',40923:'\uEDF7',40924:'\uEDF8',40925:'\uEDF9',40926:'\uEDFA',40927:'\uEDFB',40928:'\uEDFC',40929:'\uEDFD',40930:'\uEDFE',40931:'\uEDFF',40932:'\uEE00',40933:'\uEE01',40934:'\uEE02',40935:'\uEE03',40936:'\uEE04',40937:'\uEE05',40938:'\uEE06',40939:'\uEE07',40940:'\uEE08',40941:'\uEE09',40942:'\uEE0A',40943:'\uEE0B',40944:'\uEE0C',40945:'\uEE0D',40946:'\uEE0E',40947:'\uEE0F',40948:'\uEE10',40949:'\uEE11',40950:'\uEE12',40951:'\uEE13',40952:'\uEE14',40953:'\uEE15',40954:'\uEE16',40955:'\uEE17',40956:'\uEE18',40957:'\uEE19',40958:'\uEE1A',41024:'\uEE1B',41025:'\uEE1C',41026:'\uEE1D',41027:'\uEE1E',41028:'\uEE1F',41029:'\uEE20',41030:'\uEE21',41031:'\uEE22',41032:'\uEE23',41033:'\uEE24',41034:'\uEE25',41035:'\uEE26',41036:'\uEE27',41037:'\uEE28',41038:'\uEE29',41039:'\uEE2A',41040:'\uEE2B',41041:'\uEE2C',41042:'\uEE2D',41043:'\uEE2E',41044:'\uEE2F',41045:'\uEE30',41046:'\uEE31',41047:'\uEE32',41048:'\uEE33',41049:'\uEE34',41050:'\uEE35',41051:'\uEE36',41052:'\uEE37',41053:'\uEE38',41054:'\uEE39',41055:'\uEE3A',41056:'\uEE3B',41057:'\uEE3C',41058:'\uEE3D',41059:'\uEE3E',41060:'\uEE3F',41061:'\uEE40',41062:'\uEE41',41063:'\uEE42',41064:'\uEE43',41065:'\uEE44',41066:'\uEE45',41067:'\uEE46',41068:'\uEE47',41069:'\uEE48',41070:'\uEE49',41071:'\uEE4A',41072:'\uEE4B',41073:'\uEE4C',41074:'\uEE4D',41075:'\uEE4E',41076:'\uEE4F',41077:'\uEE50',41078:'\uEE51',41079:'\uEE52',41080:'\uEE53',41081:'\uEE54',41082:'\uEE55',41083:'\uEE56',41084:'\uEE57',41085:'\uEE58',41086:'\uEE59',41121:'\uEE5A',41122:'\uEE5B',41123:'\uEE5C',41124:'\uEE5D',41125:'\uEE5E',41126:'\uEE5F',41127:'\uEE60',41128:'\uEE61',41129:'\uEE62',41130:'\uEE63',41131:'\uEE64',41132:'\uEE65',41133:'\uEE66',41134:'\uEE67',41135:'\uEE68',41136:'\uEE69',41137:'\uEE6A',41138:'\uEE6B',41139:'\uEE6C',41140:'\uEE6D',41141:'\uEE6E',41142:'\uEE6F',41143:'\uEE70',41144:'\uEE71',41145:'\uEE72',41146:'\uEE73',41147:'\uEE74',41148:'\uEE75',41149:'\uEE76',41150:'\uEE77',41151:'\uEE78',41152:'\uEE79',41153:'\uEE7A',41154:'\uEE7B',41155:'\uEE7C',41156:'\uEE7D',41157:'\uEE7E',41158:'\uEE7F',41159:'\uEE80',41160:'\uEE81',41161:'\uEE82',41162:'\uEE83',41163:'\uEE84',41164:'\uEE85',41165:'\uEE86',41166:'\uEE87',41167:'\uEE88',41168:'\uEE89',41169:'\uEE8A',41170:'\uEE8B',41171:'\uEE8C',41172:'\uEE8D',41173:'\uEE8E',41174:'\uEE8F',41175:'\uEE90',41176:'\uEE91',41177:'\uEE92',41178:'\uEE93',41179:'\uEE94',41180:'\uEE95',41181:'\uEE96',41182:'\uEE97',41183:'\uEE98',41184:'\uEE99',41185:'\uEE9A',41186:'\uEE9B',41187:'\uEE9C',41188:'\uEE9D',41189:'\uEE9E',41190:'\uEE9F',41191:'\uEEA0',41192:'\uEEA1',41193:'\uEEA2',41194:'\uEEA3',41195:'\uEEA4',41196:'\uEEA5',41197:'\uEEA6',41198:'\uEEA7',41199:'\uEEA8',41200:'\uEEA9',41201:'\uEEAA',41202:'\uEEAB',41203:'\uEEAC',41204:'\uEEAD',41205:'\uEEAE',41206:'\uEEAF',41207:'\uEEB0',41208:'\uEEB1',41209:'\uEEB2',41210:'\uEEB3',41211:'\uEEB4',41212:'\uEEB5',41213:'\uEEB6',41214:'\uEEB7',41280:'\u3000',41281:'\uFF0C',41282:'\u3001',41283:'\u3002',41284:'\uFF0E',41285:'\u2027',41286:'\uFF1B',41287:'\uFF1A',41288:'\uFF1F',41289:'\uFF01',41290:'\uFE30',41291:'\u2026',41292:'\u2025',41293:'\uFE50',41294:'\uFE51',41295:'\uFE52',41296:'\u00B7',41297:'\uFE54',41298:'\uFE55',41299:'\uFE56',41300:'\uFE57',41301:'\uFF5C',41302:'\u2013',41303:'\uFE31',41304:'\u2014',41305:'\uFE33',41306:'\u2574',41307:'\uFE34',41308:'\uFE4F',41309:'\uFF08',41310:'\uFF09',41311:'\uFE35',41312:'\uFE36',41313:'\uFF5B',41314:'\uFF5D',41315:'\uFE37',41316:'\uFE38',41317:'\u3014',41318:'\u3015',41319:'\uFE39',41320:'\uFE3A',41321:'\u3010',41322:'\u3011',41323:'\uFE3B',41324:'\uFE3C',41325:'\u300A',41326:'\u300B',41327:'\uFE3D',41328:'\uFE3E',41329:'\u3008',41330:'\u3009',41331:'\uFE3F',41332:'\uFE40',41333:'\u300C',41334:'\u300D',41335:'\uFE41',41336:'\uFE42',41337:'\u300E',41338:'\u300F',41339:'\uFE43',41340:'\uFE44',41341:'\uFE59',41342:'\uFE5A',41377:'\uFE5B',41378:'\uFE5C',41379:'\uFE5D',41380:'\uFE5E',41381:'\u2018',41382:'\u2019',41383:'\u201C',41384:'\u201D',41385:'\u301D',41386:'\u301E',41387:'\u2035',41388:'\u2032',41389:'\uFF03',41390:'\uFF06',41391:'\uFF0A',41392:'\u203B',41393:'\u00A7',41394:'\u3003',41395:'\u25CB',41396:'\u25CF',41397:'\u25B3',41398:'\u25B2',41399:'\u25CE',41400:'\u2606',41401:'\u2605',41402:'\u25C7',41403:'\u25C6',41404:'\u25A1',41405:'\u25A0',41406:'\u25BD',41407:'\u25BC',41408:'\u32A3',41409:'\u2105',41410:'\u00AF',41411:'\uFFE3',41412:'\uFF3F',41413:'\u02CD',41414:'\uFE49',41415:'\uFE4A',41416:'\uFE4D',41417:'\uFE4E',41418:'\uFE4B',41419:'\uFE4C',41420:'\uFE5F',41421:'\uFE60',41422:'\uFE61',41423:'\uFF0B',41424:'\uFF0D',41425:'\u00D7',41426:'\u00F7',41427:'\u00B1',41428:'\u221A',41429:'\uFF1C',41430:'\uFF1E',41431:'\uFF1D',41432:'\u2266',41433:'\u2267',41434:'\u2260',41435:'\u221E',41436:'\u2252',41437:'\u2261',41438:'\uFE62',41439:'\uFE63',41440:'\uFE64',41441:'\uFE65',41442:'\uFE66',41443:'\uFF5E',41444:'\u2229',41445:'\u222A',41446:'\u22A5',41447:'\u2220',41448:'\u221F',41449:'\u22BF',41450:'\u33D2',41451:'\u33D1',41452:'\u222B',41453:'\u222E',41454:'\u2235',41455:'\u2234',41456:'\u2640',41457:'\u2642',41458:'\u2295',41459:'\u2299',41460:'\u2191',41461:'\u2193',41462:'\u2190',41463:'\u2192',41464:'\u2196',41465:'\u2197',41466:'\u2199',41467:'\u2198',41468:'\u2225',41469:'\u2223',41470:'\uFF0F',41536:'\uFF3C',41537:'\u2215',41538:'\uFE68',41539:'\uFF04',41540:'\uFFE5',41541:'\u3012',41542:'\uFFE0',41543:'\uFFE1',41544:'\uFF05',41545:'\uFF20',41546:'\u2103',41547:'\u2109',41548:'\uFE69',41549:'\uFE6A',41550:'\uFE6B',41551:'\u33D5',41552:'\u339C',41553:'\u339D',41554:'\u339E',41555:'\u33CE',41556:'\u33A1',41557:'\u338E',41558:'\u338F',41559:'\u33C4',41560:'\u00B0',41561:'\u5159',41562:'\u515B',41563:'\u515E',41564:'\u515D',41565:'\u5161',41566:'\u5163',41567:'\u55E7',41568:'\u74E9',41569:'\u7CCE',41570:'\u2581',41571:'\u2582',41572:'\u2583',41573:'\u2584',41574:'\u2585',41575:'\u2586',41576:'\u2587',41577:'\u2588',41578:'\u258F',41579:'\u258E',41580:'\u258D',41581:'\u258C',41582:'\u258B',41583:'\u258A',41584:'\u2589',41585:'\u253C',41586:'\u2534',41587:'\u252C',41588:'\u2524',41589:'\u251C',41590:'\u2594',41591:'\u2500',41592:'\u2502',41593:'\u2595',41594:'\u250C',41595:'\u2510',41596:'\u2514',41597:'\u2518',41598:'\u256D',41633:'\u256E',41634:'\u2570',41635:'\u256F',41636:'\u2550',41637:'\u255E',41638:'\u256A',41639:'\u2561',41640:'\u25E2',41641:'\u25E3',41642:'\u25E5',41643:'\u25E4',41644:'\u2571',41645:'\u2572',41646:'\u2573',41647:'\uFF10',41648:'\uFF11',41649:'\uFF12',41650:'\uFF13',41651:'\uFF14',41652:'\uFF15',41653:'\uFF16',41654:'\uFF17',41655:'\uFF18',41656:'\uFF19',41657:'\u2160',41658:'\u2161',41659:'\u2162',41660:'\u2163',41661:'\u2164',41662:'\u2165',41663:'\u2166',41664:'\u2167',41665:'\u2168',41666:'\u2169',41667:'\u3021',41668:'\u3022',41669:'\u3023',41670:'\u3024',41671:'\u3025',41672:'\u3026',41673:'\u3027',41674:'\u3028',41675:'\u3029',41676:'\u5341',41677:'\u5344',41678:'\u5345',41679:'\uFF21',41680:'\uFF22',41681:'\uFF23',41682:'\uFF24',41683:'\uFF25',41684:'\uFF26',41685:'\uFF27',41686:'\uFF28',41687:'\uFF29',41688:'\uFF2A',41689:'\uFF2B',41690:'\uFF2C',41691:'\uFF2D',41692:'\uFF2E',41693:'\uFF2F',41694:'\uFF30',41695:'\uFF31',41696:'\uFF32',41697:'\uFF33',41698:'\uFF34',41699:'\uFF35',41700:'\uFF36',41701:'\uFF37',41702:'\uFF38',41703:'\uFF39',41704:'\uFF3A',41705:'\uFF41',41706:'\uFF42',41707:'\uFF43',41708:'\uFF44',41709:'\uFF45',41710:'\uFF46',41711:'\uFF47',41712:'\uFF48',41713:'\uFF49',41714:'\uFF4A',41715:'\uFF4B',41716:'\uFF4C',41717:'\uFF4D',41718:'\uFF4E',41719:'\uFF4F',41720:'\uFF50',41721:'\uFF51',41722:'\uFF52',41723:'\uFF53',41724:'\uFF54',41725:'\uFF55',41726:'\uFF56',41792:'\uFF57',41793:'\uFF58',41794:'\uFF59',41795:'\uFF5A',41796:'\u0391',41797:'\u0392',41798:'\u0393',41799:'\u0394',41800:'\u0395',41801:'\u0396',41802:'\u0397',41803:'\u0398',41804:'\u0399',41805:'\u039A',41806:'\u039B',41807:'\u039C',41808:'\u039D',41809:'\u039E',41810:'\u039F',41811:'\u03A0',41812:'\u03A1',41813:'\u03A3',41814:'\u03A4',41815:'\u03A5',41816:'\u03A6',41817:'\u03A7',41818:'\u03A8',41819:'\u03A9',41820:'\u03B1',41821:'\u03B2',41822:'\u03B3',41823:'\u03B4',41824:'\u03B5',41825:'\u03B6',41826:'\u03B7',41827:'\u03B8',41828:'\u03B9',41829:'\u03BA',41830:'\u03BB',41831:'\u03BC',41832:'\u03BD',41833:'\u03BE',41834:'\u03BF',41835:'\u03C0',41836:'\u03C1',41837:'\u03C3',41838:'\u03C4',41839:'\u03C5',41840:'\u03C6',41841:'\u03C7',41842:'\u03C8',41843:'\u03C9',41844:'\u3105',41845:'\u3106',41846:'\u3107',41847:'\u3108',41848:'\u3109',41849:'\u310A',41850:'\u310B',41851:'\u310C',41852:'\u310D',41853:'\u310E',41854:'\u310F',41889:'\u3110',41890:'\u3111',41891:'\u3112',41892:'\u3113',41893:'\u3114',41894:'\u3115',41895:'\u3116',41896:'\u3117',41897:'\u3118',41898:'\u3119',41899:'\u311A',41900:'\u311B',41901:'\u311C',41902:'\u311D',41903:'\u311E',41904:'\u311F',41905:'\u3120',41906:'\u3121',41907:'\u3122',41908:'\u3123',41909:'\u3124',41910:'\u3125',41911:'\u3126',41912:'\u3127',41913:'\u3128',41914:'\u3129',41915:'\u02D9',41916:'\u02C9',41917:'\u02CA',41918:'\u02C7',41919:'\u02CB',41953:'\u20AC',42048:'\u4E00',42049:'\u4E59',42050:'\u4E01',42051:'\u4E03',42052:'\u4E43',42053:'\u4E5D',42054:'\u4E86',42055:'\u4E8C',42056:'\u4EBA',42057:'\u513F',42058:'\u5165',42059:'\u516B',42060:'\u51E0',42061:'\u5200',42062:'\u5201',42063:'\u529B',42064:'\u5315',42065:'\u5341',42066:'\u535C',42067:'\u53C8',42068:'\u4E09',42069:'\u4E0B',42070:'\u4E08',42071:'\u4E0A',42072:'\u4E2B',42073:'\u4E38',42074:'\u51E1',42075:'\u4E45',42076:'\u4E48',42077:'\u4E5F',42078:'\u4E5E',42079:'\u4E8E',42080:'\u4EA1',42081:'\u5140',42082:'\u5203',42083:'\u52FA',42084:'\u5343',42085:'\u53C9',42086:'\u53E3',42087:'\u571F',42088:'\u58EB',42089:'\u5915',42090:'\u5927',42091:'\u5973',42092:'\u5B50',42093:'\u5B51',42094:'\u5B53',42095:'\u5BF8',42096:'\u5C0F',42097:'\u5C22',42098:'\u5C38',42099:'\u5C71',42100:'\u5DDD',42101:'\u5DE5',42102:'\u5DF1',42103:'\u5DF2',42104:'\u5DF3',42105:'\u5DFE',42106:'\u5E72',42107:'\u5EFE',42108:'\u5F0B',42109:'\u5F13',42110:'\u624D',42145:'\u4E11',42146:'\u4E10',42147:'\u4E0D',42148:'\u4E2D',42149:'\u4E30',42150:'\u4E39',42151:'\u4E4B',42152:'\u5C39',42153:'\u4E88',42154:'\u4E91',42155:'\u4E95',42156:'\u4E92',42157:'\u4E94',42158:'\u4EA2',42159:'\u4EC1',42160:'\u4EC0',42161:'\u4EC3',42162:'\u4EC6',42163:'\u4EC7',42164:'\u4ECD',42165:'\u4ECA',42166:'\u4ECB',42167:'\u4EC4',42168:'\u5143',42169:'\u5141',42170:'\u5167',42171:'\u516D',42172:'\u516E',42173:'\u516C',42174:'\u5197',42175:'\u51F6',42176:'\u5206',42177:'\u5207',42178:'\u5208',42179:'\u52FB',42180:'\u52FE',42181:'\u52FF',42182:'\u5316',42183:'\u5339',42184:'\u5348',42185:'\u5347',42186:'\u5345',42187:'\u535E',42188:'\u5384',42189:'\u53CB',42190:'\u53CA',42191:'\u53CD',42192:'\u58EC',42193:'\u5929',42194:'\u592B',42195:'\u592A',42196:'\u592D',42197:'\u5B54',42198:'\u5C11',42199:'\u5C24',42200:'\u5C3A',42201:'\u5C6F',42202:'\u5DF4',42203:'\u5E7B',42204:'\u5EFF',42205:'\u5F14',42206:'\u5F15',42207:'\u5FC3',42208:'\u6208',42209:'\u6236',42210:'\u624B',42211:'\u624E',42212:'\u652F',42213:'\u6587',42214:'\u6597',42215:'\u65A4',42216:'\u65B9',42217:'\u65E5',42218:'\u66F0',42219:'\u6708',42220:'\u6728',42221:'\u6B20',42222:'\u6B62',42223:'\u6B79',42224:'\u6BCB',42225:'\u6BD4',42226:'\u6BDB',42227:'\u6C0F',42228:'\u6C34',42229:'\u706B',42230:'\u722A',42231:'\u7236',42232:'\u723B',42233:'\u7247',42234:'\u7259',42235:'\u725B',42236:'\u72AC',42237:'\u738B',42238:'\u4E19',42304:'\u4E16',42305:'\u4E15',42306:'\u4E14',42307:'\u4E18',42308:'\u4E3B',42309:'\u4E4D',42310:'\u4E4F',42311:'\u4E4E',42312:'\u4EE5',42313:'\u4ED8',42314:'\u4ED4',42315:'\u4ED5',42316:'\u4ED6',42317:'\u4ED7',42318:'\u4EE3',42319:'\u4EE4',42320:'\u4ED9',42321:'\u4EDE',42322:'\u5145',42323:'\u5144',42324:'\u5189',42325:'\u518A',42326:'\u51AC',42327:'\u51F9',42328:'\u51FA',42329:'\u51F8',42330:'\u520A',42331:'\u52A0',42332:'\u529F',42333:'\u5305',42334:'\u5306',42335:'\u5317',42336:'\u531D',42337:'\u4EDF',42338:'\u534A',42339:'\u5349',42340:'\u5361',42341:'\u5360',42342:'\u536F',42343:'\u536E',42344:'\u53BB',42345:'\u53EF',42346:'\u53E4',42347:'\u53F3',42348:'\u53EC',42349:'\u53EE',42350:'\u53E9',42351:'\u53E8',42352:'\u53FC',42353:'\u53F8',42354:'\u53F5',42355:'\u53EB',42356:'\u53E6',42357:'\u53EA',42358:'\u53F2',42359:'\u53F1',42360:'\u53F0',42361:'\u53E5',42362:'\u53ED',42363:'\u53FB',42364:'\u56DB',42365:'\u56DA',42366:'\u5916',42401:'\u592E',42402:'\u5931',42403:'\u5974',42404:'\u5976',42405:'\u5B55',42406:'\u5B83',42407:'\u5C3C',42408:'\u5DE8',42409:'\u5DE7',42410:'\u5DE6',42411:'\u5E02',42412:'\u5E03',42413:'\u5E73',42414:'\u5E7C',42415:'\u5F01',42416:'\u5F18',42417:'\u5F17',42418:'\u5FC5',42419:'\u620A',42420:'\u6253',42421:'\u6254',42422:'\u6252',42423:'\u6251',42424:'\u65A5',42425:'\u65E6',42426:'\u672E',42427:'\u672C',42428:'\u672A',42429:'\u672B',42430:'\u672D',42431:'\u6B63',42432:'\u6BCD',42433:'\u6C11',42434:'\u6C10',42435:'\u6C38',42436:'\u6C41',42437:'\u6C40',42438:'\u6C3E',42439:'\u72AF',42440:'\u7384',42441:'\u7389',42442:'\u74DC',42443:'\u74E6',42444:'\u7518',42445:'\u751F',42446:'\u7528',42447:'\u7529',42448:'\u7530',42449:'\u7531',42450:'\u7532',42451:'\u7533',42452:'\u758B',42453:'\u767D',42454:'\u76AE',42455:'\u76BF',42456:'\u76EE',42457:'\u77DB',42458:'\u77E2',42459:'\u77F3',42460:'\u793A',42461:'\u79BE',42462:'\u7A74',42463:'\u7ACB',42464:'\u4E1E',42465:'\u4E1F',42466:'\u4E52',42467:'\u4E53',42468:'\u4E69',42469:'\u4E99',42470:'\u4EA4',42471:'\u4EA6',42472:'\u4EA5',42473:'\u4EFF',42474:'\u4F09',42475:'\u4F19',42476:'\u4F0A',42477:'\u4F15',42478:'\u4F0D',42479:'\u4F10',42480:'\u4F11',42481:'\u4F0F',42482:'\u4EF2',42483:'\u4EF6',42484:'\u4EFB',42485:'\u4EF0',42486:'\u4EF3',42487:'\u4EFD',42488:'\u4F01',42489:'\u4F0B',42490:'\u5149',42491:'\u5147',42492:'\u5146',42493:'\u5148',42494:'\u5168',42560:'\u5171',42561:'\u518D',42562:'\u51B0',42563:'\u5217',42564:'\u5211',42565:'\u5212',42566:'\u520E',42567:'\u5216',42568:'\u52A3',42569:'\u5308',42570:'\u5321',42571:'\u5320',42572:'\u5370',42573:'\u5371',42574:'\u5409',42575:'\u540F',42576:'\u540C',42577:'\u540A',42578:'\u5410',42579:'\u5401',42580:'\u540B',42581:'\u5404',42582:'\u5411',42583:'\u540D',42584:'\u5408',42585:'\u5403',42586:'\u540E',42587:'\u5406',42588:'\u5412',42589:'\u56E0',42590:'\u56DE',42591:'\u56DD',42592:'\u5733',42593:'\u5730',42594:'\u5728',42595:'\u572D',42596:'\u572C',42597:'\u572F',42598:'\u5729',42599:'\u5919',42600:'\u591A',42601:'\u5937',42602:'\u5938',42603:'\u5984',42604:'\u5978',42605:'\u5983',42606:'\u597D',42607:'\u5979',42608:'\u5982',42609:'\u5981',42610:'\u5B57',42611:'\u5B58',42612:'\u5B87',42613:'\u5B88',42614:'\u5B85',42615:'\u5B89',42616:'\u5BFA',42617:'\u5C16',42618:'\u5C79',42619:'\u5DDE',42620:'\u5E06',42621:'\u5E76',42622:'\u5E74',42657:'\u5F0F',42658:'\u5F1B',42659:'\u5FD9',42660:'\u5FD6',42661:'\u620E',42662:'\u620C',42663:'\u620D',42664:'\u6210',42665:'\u6263',42666:'\u625B',42667:'\u6258',42668:'\u6536',42669:'\u65E9',42670:'\u65E8',42671:'\u65EC',42672:'\u65ED',42673:'\u66F2',42674:'\u66F3',42675:'\u6709',42676:'\u673D',42677:'\u6734',42678:'\u6731',42679:'\u6735',42680:'\u6B21',42681:'\u6B64',42682:'\u6B7B',42683:'\u6C16',42684:'\u6C5D',42685:'\u6C57',42686:'\u6C59',42687:'\u6C5F',42688:'\u6C60',42689:'\u6C50',42690:'\u6C55',42691:'\u6C61',42692:'\u6C5B',42693:'\u6C4D',42694:'\u6C4E',42695:'\u7070',42696:'\u725F',42697:'\u725D',42698:'\u767E',42699:'\u7AF9',42700:'\u7C73',42701:'\u7CF8',42702:'\u7F36',42703:'\u7F8A',42704:'\u7FBD',42705:'\u8001',42706:'\u8003',42707:'\u800C',42708:'\u8012',42709:'\u8033',42710:'\u807F',42711:'\u8089',42712:'\u808B',42713:'\u808C',42714:'\u81E3',42715:'\u81EA',42716:'\u81F3',42717:'\u81FC',42718:'\u820C',42719:'\u821B',42720:'\u821F',42721:'\u826E',42722:'\u8272',42723:'\u827E',42724:'\u866B',42725:'\u8840',42726:'\u884C',42727:'\u8863',42728:'\u897F',42729:'\u9621',42730:'\u4E32',42731:'\u4EA8',42732:'\u4F4D',42733:'\u4F4F',42734:'\u4F47',42735:'\u4F57',42736:'\u4F5E',42737:'\u4F34',42738:'\u4F5B',42739:'\u4F55',42740:'\u4F30',42741:'\u4F50',42742:'\u4F51',42743:'\u4F3D',42744:'\u4F3A',42745:'\u4F38',42746:'\u4F43',42747:'\u4F54',42748:'\u4F3C',42749:'\u4F46',42750:'\u4F63',42816:'\u4F5C',42817:'\u4F60',42818:'\u4F2F',42819:'\u4F4E',42820:'\u4F36',42821:'\u4F59',42822:'\u4F5D',42823:'\u4F48',42824:'\u4F5A',42825:'\u514C',42826:'\u514B',42827:'\u514D',42828:'\u5175',42829:'\u51B6',42830:'\u51B7',42831:'\u5225',42832:'\u5224',42833:'\u5229',42834:'\u522A',42835:'\u5228',42836:'\u52AB',42837:'\u52A9',42838:'\u52AA',42839:'\u52AC',42840:'\u5323',42841:'\u5373',42842:'\u5375',42843:'\u541D',42844:'\u542D',42845:'\u541E',42846:'\u543E',42847:'\u5426',42848:'\u544E',42849:'\u5427',42850:'\u5446',42851:'\u5443',42852:'\u5433',42853:'\u5448',42854:'\u5442',42855:'\u541B',42856:'\u5429',42857:'\u544A',42858:'\u5439',42859:'\u543B',42860:'\u5438',42861:'\u542E',42862:'\u5435',42863:'\u5436',42864:'\u5420',42865:'\u543C',42866:'\u5440',42867:'\u5431',42868:'\u542B',42869:'\u541F',42870:'\u542C',42871:'\u56EA',42872:'\u56F0',42873:'\u56E4',42874:'\u56EB',42875:'\u574A',42876:'\u5751',42877:'\u5740',42878:'\u574D',42913:'\u5747',42914:'\u574E',42915:'\u573E',42916:'\u5750',42917:'\u574F',42918:'\u573B',42919:'\u58EF',42920:'\u593E',42921:'\u599D',42922:'\u5992',42923:'\u59A8',42924:'\u599E',42925:'\u59A3',42926:'\u5999',42927:'\u5996',42928:'\u598D',42929:'\u59A4',42930:'\u5993',42931:'\u598A',42932:'\u59A5',42933:'\u5B5D',42934:'\u5B5C',42935:'\u5B5A',42936:'\u5B5B',42937:'\u5B8C',42938:'\u5B8B',42939:'\u5B8F',42940:'\u5C2C',42941:'\u5C40',42942:'\u5C41',42943:'\u5C3F',42944:'\u5C3E',42945:'\u5C90',42946:'\u5C91',42947:'\u5C94',42948:'\u5C8C',42949:'\u5DEB',42950:'\u5E0C',42951:'\u5E8F',42952:'\u5E87',42953:'\u5E8A',42954:'\u5EF7',42955:'\u5F04',42956:'\u5F1F',42957:'\u5F64',42958:'\u5F62',42959:'\u5F77',42960:'\u5F79',42961:'\u5FD8',42962:'\u5FCC',42963:'\u5FD7',42964:'\u5FCD',42965:'\u5FF1',42966:'\u5FEB',42967:'\u5FF8',42968:'\u5FEA',42969:'\u6212',42970:'\u6211',42971:'\u6284',42972:'\u6297',42973:'\u6296',42974:'\u6280',42975:'\u6276',42976:'\u6289',42977:'\u626D',42978:'\u628A',42979:'\u627C',42980:'\u627E',42981:'\u6279',42982:'\u6273',42983:'\u6292',42984:'\u626F',42985:'\u6298',42986:'\u626E',42987:'\u6295',42988:'\u6293',42989:'\u6291',42990:'\u6286',42991:'\u6539',42992:'\u653B',42993:'\u6538',42994:'\u65F1',42995:'\u66F4',42996:'\u675F',42997:'\u674E',42998:'\u674F',42999:'\u6750',43000:'\u6751',43001:'\u675C',43002:'\u6756',43003:'\u675E',43004:'\u6749',43005:'\u6746',43006:'\u6760',43072:'\u6753',43073:'\u6757',43074:'\u6B65',43075:'\u6BCF',43076:'\u6C42',43077:'\u6C5E',43078:'\u6C99',43079:'\u6C81',43080:'\u6C88',43081:'\u6C89',43082:'\u6C85',43083:'\u6C9B',43084:'\u6C6A',43085:'\u6C7A',43086:'\u6C90',43087:'\u6C70',43088:'\u6C8C',43089:'\u6C68',43090:'\u6C96',43091:'\u6C92',43092:'\u6C7D',43093:'\u6C83',43094:'\u6C72',43095:'\u6C7E',43096:'\u6C74',43097:'\u6C86',43098:'\u6C76',43099:'\u6C8D',43100:'\u6C94',43101:'\u6C98',43102:'\u6C82',43103:'\u7076',43104:'\u707C',43105:'\u707D',43106:'\u7078',43107:'\u7262',43108:'\u7261',43109:'\u7260',43110:'\u72C4',43111:'\u72C2',43112:'\u7396',43113:'\u752C',43114:'\u752B',43115:'\u7537',43116:'\u7538',43117:'\u7682',43118:'\u76EF',43119:'\u77E3',43120:'\u79C1',43121:'\u79C0',43122:'\u79BF',43123:'\u7A76',43124:'\u7CFB',43125:'\u7F55',43126:'\u8096',43127:'\u8093',43128:'\u809D',43129:'\u8098',43130:'\u809B',43131:'\u809A',43132:'\u80B2',43133:'\u826F',43134:'\u8292',43169:'\u828B',43170:'\u828D',43171:'\u898B',43172:'\u89D2',43173:'\u8A00',43174:'\u8C37',43175:'\u8C46',43176:'\u8C55',43177:'\u8C9D',43178:'\u8D64',43179:'\u8D70',43180:'\u8DB3',43181:'\u8EAB',43182:'\u8ECA',43183:'\u8F9B',43184:'\u8FB0',43185:'\u8FC2',43186:'\u8FC6',43187:'\u8FC5',43188:'\u8FC4',43189:'\u5DE1',43190:'\u9091',43191:'\u90A2',43192:'\u90AA',43193:'\u90A6',43194:'\u90A3',43195:'\u9149',43196:'\u91C6',43197:'\u91CC',43198:'\u9632',43199:'\u962E',43200:'\u9631',43201:'\u962A',43202:'\u962C',43203:'\u4E26',43204:'\u4E56',43205:'\u4E73',43206:'\u4E8B',43207:'\u4E9B',43208:'\u4E9E',43209:'\u4EAB',43210:'\u4EAC',43211:'\u4F6F',43212:'\u4F9D',43213:'\u4F8D',43214:'\u4F73',43215:'\u4F7F',43216:'\u4F6C',43217:'\u4F9B',43218:'\u4F8B',43219:'\u4F86',43220:'\u4F83',43221:'\u4F70',43222:'\u4F75',43223:'\u4F88',43224:'\u4F69',43225:'\u4F7B',43226:'\u4F96',43227:'\u4F7E',43228:'\u4F8F',43229:'\u4F91',43230:'\u4F7A',43231:'\u5154',43232:'\u5152',43233:'\u5155',43234:'\u5169',43235:'\u5177',43236:'\u5176',43237:'\u5178',43238:'\u51BD',43239:'\u51FD',43240:'\u523B',43241:'\u5238',43242:'\u5237',43243:'\u523A',43244:'\u5230',43245:'\u522E',43246:'\u5236',43247:'\u5241',43248:'\u52BE',43249:'\u52BB',43250:'\u5352',43251:'\u5354',43252:'\u5353',43253:'\u5351',43254:'\u5366',43255:'\u5377',43256:'\u5378',43257:'\u5379',43258:'\u53D6',43259:'\u53D4',43260:'\u53D7',43261:'\u5473',43262:'\u5475',43328:'\u5496',43329:'\u5478',43330:'\u5495',43331:'\u5480',43332:'\u547B',43333:'\u5477',43334:'\u5484',43335:'\u5492',43336:'\u5486',43337:'\u547C',43338:'\u5490',43339:'\u5471',43340:'\u5476',43341:'\u548C',43342:'\u549A',43343:'\u5462',43344:'\u5468',43345:'\u548B',43346:'\u547D',43347:'\u548E',43348:'\u56FA',43349:'\u5783',43350:'\u5777',43351:'\u576A',43352:'\u5769',43353:'\u5761',43354:'\u5766',43355:'\u5764',43356:'\u577C',43357:'\u591C',43358:'\u5949',43359:'\u5947',43360:'\u5948',43361:'\u5944',43362:'\u5954',43363:'\u59BE',43364:'\u59BB',43365:'\u59D4',43366:'\u59B9',43367:'\u59AE',43368:'\u59D1',43369:'\u59C6',43370:'\u59D0',43371:'\u59CD',43372:'\u59CB',43373:'\u59D3',43374:'\u59CA',43375:'\u59AF',43376:'\u59B3',43377:'\u59D2',43378:'\u59C5',43379:'\u5B5F',43380:'\u5B64',43381:'\u5B63',43382:'\u5B97',43383:'\u5B9A',43384:'\u5B98',43385:'\u5B9C',43386:'\u5B99',43387:'\u5B9B',43388:'\u5C1A',43389:'\u5C48',43390:'\u5C45',43425:'\u5C46',43426:'\u5CB7',43427:'\u5CA1',43428:'\u5CB8',43429:'\u5CA9',43430:'\u5CAB',43431:'\u5CB1',43432:'\u5CB3',43433:'\u5E18',43434:'\u5E1A',43435:'\u5E16',43436:'\u5E15',43437:'\u5E1B',43438:'\u5E11',43439:'\u5E78',43440:'\u5E9A',43441:'\u5E97',43442:'\u5E9C',43443:'\u5E95',43444:'\u5E96',43445:'\u5EF6',43446:'\u5F26',43447:'\u5F27',43448:'\u5F29',43449:'\u5F80',43450:'\u5F81',43451:'\u5F7F',43452:'\u5F7C',43453:'\u5FDD',43454:'\u5FE0',43455:'\u5FFD',43456:'\u5FF5',43457:'\u5FFF',43458:'\u600F',43459:'\u6014',43460:'\u602F',43461:'\u6035',43462:'\u6016',43463:'\u602A',43464:'\u6015',43465:'\u6021',43466:'\u6027',43467:'\u6029',43468:'\u602B',43469:'\u601B',43470:'\u6216',43471:'\u6215',43472:'\u623F',43473:'\u623E',43474:'\u6240',43475:'\u627F',43476:'\u62C9',43477:'\u62CC',43478:'\u62C4',43479:'\u62BF',43480:'\u62C2',43481:'\u62B9',43482:'\u62D2',43483:'\u62DB',43484:'\u62AB',43485:'\u62D3',43486:'\u62D4',43487:'\u62CB',43488:'\u62C8',43489:'\u62A8',43490:'\u62BD',43491:'\u62BC',43492:'\u62D0',43493:'\u62D9',43494:'\u62C7',43495:'\u62CD',43496:'\u62B5',43497:'\u62DA',43498:'\u62B1',43499:'\u62D8',43500:'\u62D6',43501:'\u62D7',43502:'\u62C6',43503:'\u62AC',43504:'\u62CE',43505:'\u653E',43506:'\u65A7',43507:'\u65BC',43508:'\u65FA',43509:'\u6614',43510:'\u6613',43511:'\u660C',43512:'\u6606',43513:'\u6602',43514:'\u660E',43515:'\u6600',43516:'\u660F',43517:'\u6615',43518:'\u660A',43584:'\u6607',43585:'\u670D',43586:'\u670B',43587:'\u676D',43588:'\u678B',43589:'\u6795',43590:'\u6771',43591:'\u679C',43592:'\u6773',43593:'\u6777',43594:'\u6787',43595:'\u679D',43596:'\u6797',43597:'\u676F',43598:'\u6770',43599:'\u677F',43600:'\u6789',43601:'\u677E',43602:'\u6790',43603:'\u6775',43604:'\u679A',43605:'\u6793',43606:'\u677C',43607:'\u676A',43608:'\u6772',43609:'\u6B23',43610:'\u6B66',43611:'\u6B67',43612:'\u6B7F',43613:'\u6C13',43614:'\u6C1B',43615:'\u6CE3',43616:'\u6CE8',43617:'\u6CF3',43618:'\u6CB1',43619:'\u6CCC',43620:'\u6CE5',43621:'\u6CB3',43622:'\u6CBD',43623:'\u6CBE',43624:'\u6CBC',43625:'\u6CE2',43626:'\u6CAB',43627:'\u6CD5',43628:'\u6CD3',43629:'\u6CB8',43630:'\u6CC4',43631:'\u6CB9',43632:'\u6CC1',43633:'\u6CAE',43634:'\u6CD7',43635:'\u6CC5',43636:'\u6CF1',43637:'\u6CBF',43638:'\u6CBB',43639:'\u6CE1',43640:'\u6CDB',43641:'\u6CCA',43642:'\u6CAC',43643:'\u6CEF',43644:'\u6CDC',43645:'\u6CD6',43646:'\u6CE0',43681:'\u7095',43682:'\u708E',43683:'\u7092',43684:'\u708A',43685:'\u7099',43686:'\u722C',43687:'\u722D',43688:'\u7238',43689:'\u7248',43690:'\u7267',43691:'\u7269',43692:'\u72C0',43693:'\u72CE',43694:'\u72D9',43695:'\u72D7',43696:'\u72D0',43697:'\u73A9',43698:'\u73A8',43699:'\u739F',43700:'\u73AB',43701:'\u73A5',43702:'\u753D',43703:'\u759D',43704:'\u7599',43705:'\u759A',43706:'\u7684',43707:'\u76C2',43708:'\u76F2',43709:'\u76F4',43710:'\u77E5',43711:'\u77FD',43712:'\u793E',43713:'\u7940',43714:'\u7941',43715:'\u79C9',43716:'\u79C8',43717:'\u7A7A',43718:'\u7A79',43719:'\u7AFA',43720:'\u7CFE',43721:'\u7F54',43722:'\u7F8C',43723:'\u7F8B',43724:'\u8005',43725:'\u80BA',43726:'\u80A5',43727:'\u80A2',43728:'\u80B1',43729:'\u80A1',43730:'\u80AB',43731:'\u80A9',43732:'\u80B4',43733:'\u80AA',43734:'\u80AF',43735:'\u81E5',43736:'\u81FE',43737:'\u820D',43738:'\u82B3',43739:'\u829D',43740:'\u8299',43741:'\u82AD',43742:'\u82BD',43743:'\u829F',43744:'\u82B9',43745:'\u82B1',43746:'\u82AC',43747:'\u82A5',43748:'\u82AF',43749:'\u82B8',43750:'\u82A3',43751:'\u82B0',43752:'\u82BE',43753:'\u82B7',43754:'\u864E',43755:'\u8671',43756:'\u521D',43757:'\u8868',43758:'\u8ECB',43759:'\u8FCE',43760:'\u8FD4',43761:'\u8FD1',43762:'\u90B5',43763:'\u90B8',43764:'\u90B1',43765:'\u90B6',43766:'\u91C7',43767:'\u91D1',43768:'\u9577',43769:'\u9580',43770:'\u961C',43771:'\u9640',43772:'\u963F',43773:'\u963B',43774:'\u9644',43840:'\u9642',43841:'\u96B9',43842:'\u96E8',43843:'\u9752',43844:'\u975E',43845:'\u4E9F',43846:'\u4EAD',43847:'\u4EAE',43848:'\u4FE1',43849:'\u4FB5',43850:'\u4FAF',43851:'\u4FBF',43852:'\u4FE0',43853:'\u4FD1',43854:'\u4FCF',43855:'\u4FDD',43856:'\u4FC3',43857:'\u4FB6',43858:'\u4FD8',43859:'\u4FDF',43860:'\u4FCA',43861:'\u4FD7',43862:'\u4FAE',43863:'\u4FD0',43864:'\u4FC4',43865:'\u4FC2',43866:'\u4FDA',43867:'\u4FCE',43868:'\u4FDE',43869:'\u4FB7',43870:'\u5157',43871:'\u5192',43872:'\u5191',43873:'\u51A0',43874:'\u524E',43875:'\u5243',43876:'\u524A',43877:'\u524D',43878:'\u524C',43879:'\u524B',43880:'\u5247',43881:'\u52C7',43882:'\u52C9',43883:'\u52C3',43884:'\u52C1',43885:'\u530D',43886:'\u5357',43887:'\u537B',43888:'\u539A',43889:'\u53DB',43890:'\u54AC',43891:'\u54C0',43892:'\u54A8',43893:'\u54CE',43894:'\u54C9',43895:'\u54B8',43896:'\u54A6',43897:'\u54B3',43898:'\u54C7',43899:'\u54C2',43900:'\u54BD',43901:'\u54AA',43902:'\u54C1',43937:'\u54C4',43938:'\u54C8',43939:'\u54AF',43940:'\u54AB',43941:'\u54B1',43942:'\u54BB',43943:'\u54A9',43944:'\u54A7',43945:'\u54BF',43946:'\u56FF',43947:'\u5782',43948:'\u578B',43949:'\u57A0',43950:'\u57A3',43951:'\u57A2',43952:'\u57CE',43953:'\u57AE',43954:'\u5793',43955:'\u5955',43956:'\u5951',43957:'\u594F',43958:'\u594E',43959:'\u5950',43960:'\u59DC',43961:'\u59D8',43962:'\u59FF',43963:'\u59E3',43964:'\u59E8',43965:'\u5A03',43966:'\u59E5',43967:'\u59EA',43968:'\u59DA',43969:'\u59E6',43970:'\u5A01',43971:'\u59FB',43972:'\u5B69',43973:'\u5BA3',43974:'\u5BA6',43975:'\u5BA4',43976:'\u5BA2',43977:'\u5BA5',43978:'\u5C01',43979:'\u5C4E',43980:'\u5C4F',43981:'\u5C4D',43982:'\u5C4B',43983:'\u5CD9',43984:'\u5CD2',43985:'\u5DF7',43986:'\u5E1D',43987:'\u5E25',43988:'\u5E1F',43989:'\u5E7D',43990:'\u5EA0',43991:'\u5EA6',43992:'\u5EFA',43993:'\u5F08',43994:'\u5F2D',43995:'\u5F65',43996:'\u5F88',43997:'\u5F85',43998:'\u5F8A',43999:'\u5F8B',44000:'\u5F87',44001:'\u5F8C',44002:'\u5F89',44003:'\u6012',44004:'\u601D',44005:'\u6020',44006:'\u6025',44007:'\u600E',44008:'\u6028',44009:'\u604D',44010:'\u6070',44011:'\u6068',44012:'\u6062',44013:'\u6046',44014:'\u6043',44015:'\u606C',44016:'\u606B',44017:'\u606A',44018:'\u6064',44019:'\u6241',44020:'\u62DC',44021:'\u6316',44022:'\u6309',44023:'\u62FC',44024:'\u62ED',44025:'\u6301',44026:'\u62EE',44027:'\u62FD',44028:'\u6307',44029:'\u62F1',44030:'\u62F7',44096:'\u62EF',44097:'\u62EC',44098:'\u62FE',44099:'\u62F4',44100:'\u6311',44101:'\u6302',44102:'\u653F',44103:'\u6545',44104:'\u65AB',44105:'\u65BD',44106:'\u65E2',44107:'\u6625',44108:'\u662D',44109:'\u6620',44110:'\u6627',44111:'\u662F',44112:'\u661F',44113:'\u6628',44114:'\u6631',44115:'\u6624',44116:'\u66F7',44117:'\u67FF',44118:'\u67D3',44119:'\u67F1',44120:'\u67D4',44121:'\u67D0',44122:'\u67EC',44123:'\u67B6',44124:'\u67AF',44125:'\u67F5',44126:'\u67E9',44127:'\u67EF',44128:'\u67C4',44129:'\u67D1',44130:'\u67B4',44131:'\u67DA',44132:'\u67E5',44133:'\u67B8',44134:'\u67CF',44135:'\u67DE',44136:'\u67F3',44137:'\u67B0',44138:'\u67D9',44139:'\u67E2',44140:'\u67DD',44141:'\u67D2',44142:'\u6B6A',44143:'\u6B83',44144:'\u6B86',44145:'\u6BB5',44146:'\u6BD2',44147:'\u6BD7',44148:'\u6C1F',44149:'\u6CC9',44150:'\u6D0B',44151:'\u6D32',44152:'\u6D2A',44153:'\u6D41',44154:'\u6D25',44155:'\u6D0C',44156:'\u6D31',44157:'\u6D1E',44158:'\u6D17',44193:'\u6D3B',44194:'\u6D3D',44195:'\u6D3E',44196:'\u6D36',44197:'\u6D1B',44198:'\u6CF5',44199:'\u6D39',44200:'\u6D27',44201:'\u6D38',44202:'\u6D29',44203:'\u6D2E',44204:'\u6D35',44205:'\u6D0E',44206:'\u6D2B',44207:'\u70AB',44208:'\u70BA',44209:'\u70B3',44210:'\u70AC',44211:'\u70AF',44212:'\u70AD',44213:'\u70B8',44214:'\u70AE',44215:'\u70A4',44216:'\u7230',44217:'\u7272',44218:'\u726F',44219:'\u7274',44220:'\u72E9',44221:'\u72E0',44222:'\u72E1',44223:'\u73B7',44224:'\u73CA',44225:'\u73BB',44226:'\u73B2',44227:'\u73CD',44228:'\u73C0',44229:'\u73B3',44230:'\u751A',44231:'\u752D',44232:'\u754F',44233:'\u754C',44234:'\u754E',44235:'\u754B',44236:'\u75AB',44237:'\u75A4',44238:'\u75A5',44239:'\u75A2',44240:'\u75A3',44241:'\u7678',44242:'\u7686',44243:'\u7687',44244:'\u7688',44245:'\u76C8',44246:'\u76C6',44247:'\u76C3',44248:'\u76C5',44249:'\u7701',44250:'\u76F9',44251:'\u76F8',44252:'\u7709',44253:'\u770B',44254:'\u76FE',44255:'\u76FC',44256:'\u7707',44257:'\u77DC',44258:'\u7802',44259:'\u7814',44260:'\u780C',44261:'\u780D',44262:'\u7946',44263:'\u7949',44264:'\u7948',44265:'\u7947',44266:'\u79B9',44267:'\u79BA',44268:'\u79D1',44269:'\u79D2',44270:'\u79CB',44271:'\u7A7F',44272:'\u7A81',44273:'\u7AFF',44274:'\u7AFD',44275:'\u7C7D',44276:'\u7D02',44277:'\u7D05',44278:'\u7D00',44279:'\u7D09',44280:'\u7D07',44281:'\u7D04',44282:'\u7D06',44283:'\u7F38',44284:'\u7F8E',44285:'\u7FBF',44286:'\u8004',44352:'\u8010',44353:'\u800D',44354:'\u8011',44355:'\u8036',44356:'\u80D6',44357:'\u80E5',44358:'\u80DA',44359:'\u80C3',44360:'\u80C4',44361:'\u80CC',44362:'\u80E1',44363:'\u80DB',44364:'\u80CE',44365:'\u80DE',44366:'\u80E4',44367:'\u80DD',44368:'\u81F4',44369:'\u8222',44370:'\u82E7',44371:'\u8303',44372:'\u8305',44373:'\u82E3',44374:'\u82DB',44375:'\u82E6',44376:'\u8304',44377:'\u82E5',44378:'\u8302',44379:'\u8309',44380:'\u82D2',44381:'\u82D7',44382:'\u82F1',44383:'\u8301',44384:'\u82DC',44385:'\u82D4',44386:'\u82D1',44387:'\u82DE',44388:'\u82D3',44389:'\u82DF',44390:'\u82EF',44391:'\u8306',44392:'\u8650',44393:'\u8679',44394:'\u867B',44395:'\u867A',44396:'\u884D',44397:'\u886B',44398:'\u8981',44399:'\u89D4',44400:'\u8A08',44401:'\u8A02',44402:'\u8A03',44403:'\u8C9E',44404:'\u8CA0',44405:'\u8D74',44406:'\u8D73',44407:'\u8DB4',44408:'\u8ECD',44409:'\u8ECC',44410:'\u8FF0',44411:'\u8FE6',44412:'\u8FE2',44413:'\u8FEA',44414:'\u8FE5',44449:'\u8FED',44450:'\u8FEB',44451:'\u8FE4',44452:'\u8FE8',44453:'\u90CA',44454:'\u90CE',44455:'\u90C1',44456:'\u90C3',44457:'\u914B',44458:'\u914A',44459:'\u91CD',44460:'\u9582',44461:'\u9650',44462:'\u964B',44463:'\u964C',44464:'\u964D',44465:'\u9762',44466:'\u9769',44467:'\u97CB',44468:'\u97ED',44469:'\u97F3',44470:'\u9801',44471:'\u98A8',44472:'\u98DB',44473:'\u98DF',44474:'\u9996',44475:'\u9999',44476:'\u4E58',44477:'\u4EB3',44478:'\u500C',44479:'\u500D',44480:'\u5023',44481:'\u4FEF',44482:'\u5026',44483:'\u5025',44484:'\u4FF8',44485:'\u5029',44486:'\u5016',44487:'\u5006',44488:'\u503C',44489:'\u501F',44490:'\u501A',44491:'\u5012',44492:'\u5011',44493:'\u4FFA',44494:'\u5000',44495:'\u5014',44496:'\u5028',44497:'\u4FF1',44498:'\u5021',44499:'\u500B',44500:'\u5019',44501:'\u5018',44502:'\u4FF3',44503:'\u4FEE',44504:'\u502D',44505:'\u502A',44506:'\u4FFE',44507:'\u502B',44508:'\u5009',44509:'\u517C',44510:'\u51A4',44511:'\u51A5',44512:'\u51A2',44513:'\u51CD',44514:'\u51CC',44515:'\u51C6',44516:'\u51CB',44517:'\u5256',44518:'\u525C',44519:'\u5254',44520:'\u525B',44521:'\u525D',44522:'\u532A',44523:'\u537F',44524:'\u539F',44525:'\u539D',44526:'\u53DF',44527:'\u54E8',44528:'\u5510',44529:'\u5501',44530:'\u5537',44531:'\u54FC',44532:'\u54E5',44533:'\u54F2',44534:'\u5506',44535:'\u54FA',44536:'\u5514',44537:'\u54E9',44538:'\u54ED',44539:'\u54E1',44540:'\u5509',44541:'\u54EE',44542:'\u54EA',44608:'\u54E6',44609:'\u5527',44610:'\u5507',44611:'\u54FD',44612:'\u550F',44613:'\u5703',44614:'\u5704',44615:'\u57C2',44616:'\u57D4',44617:'\u57CB',44618:'\u57C3',44619:'\u5809',44620:'\u590F',44621:'\u5957',44622:'\u5958',44623:'\u595A',44624:'\u5A11',44625:'\u5A18',44626:'\u5A1C',44627:'\u5A1F',44628:'\u5A1B',44629:'\u5A13',44630:'\u59EC',44631:'\u5A20',44632:'\u5A23',44633:'\u5A29',44634:'\u5A25',44635:'\u5A0C',44636:'\u5A09',44637:'\u5B6B',44638:'\u5C58',44639:'\u5BB0',44640:'\u5BB3',44641:'\u5BB6',44642:'\u5BB4',44643:'\u5BAE',44644:'\u5BB5',44645:'\u5BB9',44646:'\u5BB8',44647:'\u5C04',44648:'\u5C51',44649:'\u5C55',44650:'\u5C50',44651:'\u5CED',44652:'\u5CFD',44653:'\u5CFB',44654:'\u5CEA',44655:'\u5CE8',44656:'\u5CF0',44657:'\u5CF6',44658:'\u5D01',44659:'\u5CF4',44660:'\u5DEE',44661:'\u5E2D',44662:'\u5E2B',44663:'\u5EAB',44664:'\u5EAD',44665:'\u5EA7',44666:'\u5F31',44667:'\u5F92',44668:'\u5F91',44669:'\u5F90',44670:'\u6059',44705:'\u6063',44706:'\u6065',44707:'\u6050',44708:'\u6055',44709:'\u606D',44710:'\u6069',44711:'\u606F',44712:'\u6084',44713:'\u609F',44714:'\u609A',44715:'\u608D',44716:'\u6094',44717:'\u608C',44718:'\u6085',44719:'\u6096',44720:'\u6247',44721:'\u62F3',44722:'\u6308',44723:'\u62FF',44724:'\u634E',44725:'\u633E',44726:'\u632F',44727:'\u6355',44728:'\u6342',44729:'\u6346',44730:'\u634F',44731:'\u6349',44732:'\u633A',44733:'\u6350',44734:'\u633D',44735:'\u632A',44736:'\u632B',44737:'\u6328',44738:'\u634D',44739:'\u634C',44740:'\u6548',44741:'\u6549',44742:'\u6599',44743:'\u65C1',44744:'\u65C5',44745:'\u6642',44746:'\u6649',44747:'\u664F',44748:'\u6643',44749:'\u6652',44750:'\u664C',44751:'\u6645',44752:'\u6641',44753:'\u66F8',44754:'\u6714',44755:'\u6715',44756:'\u6717',44757:'\u6821',44758:'\u6838',44759:'\u6848',44760:'\u6846',44761:'\u6853',44762:'\u6839',44763:'\u6842',44764:'\u6854',44765:'\u6829',44766:'\u68B3',44767:'\u6817',44768:'\u684C',44769:'\u6851',44770:'\u683D',44771:'\u67F4',44772:'\u6850',44773:'\u6840',44774:'\u683C',44775:'\u6843',44776:'\u682A',44777:'\u6845',44778:'\u6813',44779:'\u6818',44780:'\u6841',44781:'\u6B8A',44782:'\u6B89',44783:'\u6BB7',44784:'\u6C23',44785:'\u6C27',44786:'\u6C28',44787:'\u6C26',44788:'\u6C24',44789:'\u6CF0',44790:'\u6D6A',44791:'\u6D95',44792:'\u6D88',44793:'\u6D87',44794:'\u6D66',44795:'\u6D78',44796:'\u6D77',44797:'\u6D59',44798:'\u6D93',44864:'\u6D6C',44865:'\u6D89',44866:'\u6D6E',44867:'\u6D5A',44868:'\u6D74',44869:'\u6D69',44870:'\u6D8C',44871:'\u6D8A',44872:'\u6D79',44873:'\u6D85',44874:'\u6D65',44875:'\u6D94',44876:'\u70CA',44877:'\u70D8',44878:'\u70E4',44879:'\u70D9',44880:'\u70C8',44881:'\u70CF',44882:'\u7239',44883:'\u7279',44884:'\u72FC',44885:'\u72F9',44886:'\u72FD',44887:'\u72F8',44888:'\u72F7',44889:'\u7386',44890:'\u73ED',44891:'\u7409',44892:'\u73EE',44893:'\u73E0',44894:'\u73EA',44895:'\u73DE',44896:'\u7554',44897:'\u755D',44898:'\u755C',44899:'\u755A',44900:'\u7559',44901:'\u75BE',44902:'\u75C5',44903:'\u75C7',44904:'\u75B2',44905:'\u75B3',44906:'\u75BD',44907:'\u75BC',44908:'\u75B9',44909:'\u75C2',44910:'\u75B8',44911:'\u768B',44912:'\u76B0',44913:'\u76CA',44914:'\u76CD',44915:'\u76CE',44916:'\u7729',44917:'\u771F',44918:'\u7720',44919:'\u7728',44920:'\u77E9',44921:'\u7830',44922:'\u7827',44923:'\u7838',44924:'\u781D',44925:'\u7834',44926:'\u7837',44961:'\u7825',44962:'\u782D',44963:'\u7820',44964:'\u781F',44965:'\u7832',44966:'\u7955',44967:'\u7950',44968:'\u7960',44969:'\u795F',44970:'\u7956',44971:'\u795E',44972:'\u795D',44973:'\u7957',44974:'\u795A',44975:'\u79E4',44976:'\u79E3',44977:'\u79E7',44978:'\u79DF',44979:'\u79E6',44980:'\u79E9',44981:'\u79D8',44982:'\u7A84',44983:'\u7A88',44984:'\u7AD9',44985:'\u7B06',44986:'\u7B11',44987:'\u7C89',44988:'\u7D21',44989:'\u7D17',44990:'\u7D0B',44991:'\u7D0A',44992:'\u7D20',44993:'\u7D22',44994:'\u7D14',44995:'\u7D10',44996:'\u7D15',44997:'\u7D1A',44998:'\u7D1C',44999:'\u7D0D',45000:'\u7D19',45001:'\u7D1B',45002:'\u7F3A',45003:'\u7F5F',45004:'\u7F94',45005:'\u7FC5',45006:'\u7FC1',45007:'\u8006',45008:'\u8018',45009:'\u8015',45010:'\u8019',45011:'\u8017',45012:'\u803D',45013:'\u803F',45014:'\u80F1',45015:'\u8102',45016:'\u80F0',45017:'\u8105',45018:'\u80ED',45019:'\u80F4',45020:'\u8106',45021:'\u80F8',45022:'\u80F3',45023:'\u8108',45024:'\u80FD',45025:'\u810A',45026:'\u80FC',45027:'\u80EF',45028:'\u81ED',45029:'\u81EC',45030:'\u8200',45031:'\u8210',45032:'\u822A',45033:'\u822B',45034:'\u8228',45035:'\u822C',45036:'\u82BB',45037:'\u832B',45038:'\u8352',45039:'\u8354',45040:'\u834A',45041:'\u8338',45042:'\u8350',45043:'\u8349',45044:'\u8335',45045:'\u8334',45046:'\u834F',45047:'\u8332',45048:'\u8339',45049:'\u8336',45050:'\u8317',45051:'\u8340',45052:'\u8331',45053:'\u8328',45054:'\u8343',45120:'\u8654',45121:'\u868A',45122:'\u86AA',45123:'\u8693',45124:'\u86A4',45125:'\u86A9',45126:'\u868C',45127:'\u86A3',45128:'\u869C',45129:'\u8870',45130:'\u8877',45131:'\u8881',45132:'\u8882',45133:'\u887D',45134:'\u8879',45135:'\u8A18',45136:'\u8A10',45137:'\u8A0E',45138:'\u8A0C',45139:'\u8A15',45140:'\u8A0A',45141:'\u8A17',45142:'\u8A13',45143:'\u8A16',45144:'\u8A0F',45145:'\u8A11',45146:'\u8C48',45147:'\u8C7A',45148:'\u8C79',45149:'\u8CA1',45150:'\u8CA2',45151:'\u8D77',45152:'\u8EAC',45153:'\u8ED2',45154:'\u8ED4',45155:'\u8ECF',45156:'\u8FB1',45157:'\u9001',45158:'\u9006',45159:'\u8FF7',45160:'\u9000',45161:'\u8FFA',45162:'\u8FF4',45163:'\u9003',45164:'\u8FFD',45165:'\u9005',45166:'\u8FF8',45167:'\u9095',45168:'\u90E1',45169:'\u90DD',45170:'\u90E2',45171:'\u9152',45172:'\u914D',45173:'\u914C',45174:'\u91D8',45175:'\u91DD',45176:'\u91D7',45177:'\u91DC',45178:'\u91D9',45179:'\u9583',45180:'\u9662',45181:'\u9663',45182:'\u9661',45217:'\u965B',45218:'\u965D',45219:'\u9664',45220:'\u9658',45221:'\u965E',45222:'\u96BB',45223:'\u98E2',45224:'\u99AC',45225:'\u9AA8',45226:'\u9AD8',45227:'\u9B25',45228:'\u9B32',45229:'\u9B3C',45230:'\u4E7E',45231:'\u507A',45232:'\u507D',45233:'\u505C',45234:'\u5047',45235:'\u5043',45236:'\u504C',45237:'\u505A',45238:'\u5049',45239:'\u5065',45240:'\u5076',45241:'\u504E',45242:'\u5055',45243:'\u5075',45244:'\u5074',45245:'\u5077',45246:'\u504F',45247:'\u500F',45248:'\u506F',45249:'\u506D',45250:'\u515C',45251:'\u5195',45252:'\u51F0',45253:'\u526A',45254:'\u526F',45255:'\u52D2',45256:'\u52D9',45257:'\u52D8',45258:'\u52D5',45259:'\u5310',45260:'\u530F',45261:'\u5319',45262:'\u533F',45263:'\u5340',45264:'\u533E',45265:'\u53C3',45266:'\u66FC',45267:'\u5546',45268:'\u556A',45269:'\u5566',45270:'\u5544',45271:'\u555E',45272:'\u5561',45273:'\u5543',45274:'\u554A',45275:'\u5531',45276:'\u5556',45277:'\u554F',45278:'\u5555',45279:'\u552F',45280:'\u5564',45281:'\u5538',45282:'\u552E',45283:'\u555C',45284:'\u552C',45285:'\u5563',45286:'\u5533',45287:'\u5541',45288:'\u5557',45289:'\u5708',45290:'\u570B',45291:'\u5709',45292:'\u57DF',45293:'\u5805',45294:'\u580A',45295:'\u5806',45296:'\u57E0',45297:'\u57E4',45298:'\u57FA',45299:'\u5802',45300:'\u5835',45301:'\u57F7',45302:'\u57F9',45303:'\u5920',45304:'\u5962',45305:'\u5A36',45306:'\u5A41',45307:'\u5A49',45308:'\u5A66',45309:'\u5A6A',45310:'\u5A40',45376:'\u5A3C',45377:'\u5A62',45378:'\u5A5A',45379:'\u5A46',45380:'\u5A4A',45381:'\u5B70',45382:'\u5BC7',45383:'\u5BC5',45384:'\u5BC4',45385:'\u5BC2',45386:'\u5BBF',45387:'\u5BC6',45388:'\u5C09',45389:'\u5C08',45390:'\u5C07',45391:'\u5C60',45392:'\u5C5C',45393:'\u5C5D',45394:'\u5D07',45395:'\u5D06',45396:'\u5D0E',45397:'\u5D1B',45398:'\u5D16',45399:'\u5D22',45400:'\u5D11',45401:'\u5D29',45402:'\u5D14',45403:'\u5D19',45404:'\u5D24',45405:'\u5D27',45406:'\u5D17',45407:'\u5DE2',45408:'\u5E38',45409:'\u5E36',45410:'\u5E33',45411:'\u5E37',45412:'\u5EB7',45413:'\u5EB8',45414:'\u5EB6',45415:'\u5EB5',45416:'\u5EBE',45417:'\u5F35',45418:'\u5F37',45419:'\u5F57',45420:'\u5F6C',45421:'\u5F69',45422:'\u5F6B',45423:'\u5F97',45424:'\u5F99',45425:'\u5F9E',45426:'\u5F98',45427:'\u5FA1',45428:'\u5FA0',45429:'\u5F9C',45430:'\u607F',45431:'\u60A3',45432:'\u6089',45433:'\u60A0',45434:'\u60A8',45435:'\u60CB',45436:'\u60B4',45437:'\u60E6',45438:'\u60BD',45473:'\u60C5',45474:'\u60BB',45475:'\u60B5',45476:'\u60DC',45477:'\u60BC',45478:'\u60D8',45479:'\u60D5',45480:'\u60C6',45481:'\u60DF',45482:'\u60B8',45483:'\u60DA',45484:'\u60C7',45485:'\u621A',45486:'\u621B',45487:'\u6248',45488:'\u63A0',45489:'\u63A7',45490:'\u6372',45491:'\u6396',45492:'\u63A2',45493:'\u63A5',45494:'\u6377',45495:'\u6367',45496:'\u6398',45497:'\u63AA',45498:'\u6371',45499:'\u63A9',45500:'\u6389',45501:'\u6383',45502:'\u639B',45503:'\u636B',45504:'\u63A8',45505:'\u6384',45506:'\u6388',45507:'\u6399',45508:'\u63A1',45509:'\u63AC',45510:'\u6392',45511:'\u638F',45512:'\u6380',45513:'\u637B',45514:'\u6369',45515:'\u6368',45516:'\u637A',45517:'\u655D',45518:'\u6556',45519:'\u6551',45520:'\u6559',45521:'\u6557',45522:'\u555F',45523:'\u654F',45524:'\u6558',45525:'\u6555',45526:'\u6554',45527:'\u659C',45528:'\u659B',45529:'\u65AC',45530:'\u65CF',45531:'\u65CB',45532:'\u65CC',45533:'\u65CE',45534:'\u665D',45535:'\u665A',45536:'\u6664',45537:'\u6668',45538:'\u6666',45539:'\u665E',45540:'\u66F9',45541:'\u52D7',45542:'\u671B',45543:'\u6881',45544:'\u68AF',45545:'\u68A2',45546:'\u6893',45547:'\u68B5',45548:'\u687F',45549:'\u6876',45550:'\u68B1',45551:'\u68A7',45552:'\u6897',45553:'\u68B0',45554:'\u6883',45555:'\u68C4',45556:'\u68AD',45557:'\u6886',45558:'\u6885',45559:'\u6894',45560:'\u689D',45561:'\u68A8',45562:'\u689F',45563:'\u68A1',45564:'\u6882',45565:'\u6B32',45566:'\u6BBA',45632:'\u6BEB',45633:'\u6BEC',45634:'\u6C2B',45635:'\u6D8E',45636:'\u6DBC',45637:'\u6DF3',45638:'\u6DD9',45639:'\u6DB2',45640:'\u6DE1',45641:'\u6DCC',45642:'\u6DE4',45643:'\u6DFB',45644:'\u6DFA',45645:'\u6E05',45646:'\u6DC7',45647:'\u6DCB',45648:'\u6DAF',45649:'\u6DD1',45650:'\u6DAE',45651:'\u6DDE',45652:'\u6DF9',45653:'\u6DB8',45654:'\u6DF7',45655:'\u6DF5',45656:'\u6DC5',45657:'\u6DD2',45658:'\u6E1A',45659:'\u6DB5',45660:'\u6DDA',45661:'\u6DEB',45662:'\u6DD8',45663:'\u6DEA',45664:'\u6DF1',45665:'\u6DEE',45666:'\u6DE8',45667:'\u6DC6',45668:'\u6DC4',45669:'\u6DAA',45670:'\u6DEC',45671:'\u6DBF',45672:'\u6DE6',45673:'\u70F9',45674:'\u7109',45675:'\u710A',45676:'\u70FD',45677:'\u70EF',45678:'\u723D',45679:'\u727D',45680:'\u7281',45681:'\u731C',45682:'\u731B',45683:'\u7316',45684:'\u7313',45685:'\u7319',45686:'\u7387',45687:'\u7405',45688:'\u740A',45689:'\u7403',45690:'\u7406',45691:'\u73FE',45692:'\u740D',45693:'\u74E0',45694:'\u74F6',45729:'\u74F7',45730:'\u751C',45731:'\u7522',45732:'\u7565',45733:'\u7566',45734:'\u7562',45735:'\u7570',45736:'\u758F',45737:'\u75D4',45738:'\u75D5',45739:'\u75B5',45740:'\u75CA',45741:'\u75CD',45742:'\u768E',45743:'\u76D4',45744:'\u76D2',45745:'\u76DB',45746:'\u7737',45747:'\u773E',45748:'\u773C',45749:'\u7736',45750:'\u7738',45751:'\u773A',45752:'\u786B',45753:'\u7843',45754:'\u784E',45755:'\u7965',45756:'\u7968',45757:'\u796D',45758:'\u79FB',45759:'\u7A92',45760:'\u7A95',45761:'\u7B20',45762:'\u7B28',45763:'\u7B1B',45764:'\u7B2C',45765:'\u7B26',45766:'\u7B19',45767:'\u7B1E',45768:'\u7B2E',45769:'\u7C92',45770:'\u7C97',45771:'\u7C95',45772:'\u7D46',45773:'\u7D43',45774:'\u7D71',45775:'\u7D2E',45776:'\u7D39',45777:'\u7D3C',45778:'\u7D40',45779:'\u7D30',45780:'\u7D33',45781:'\u7D44',45782:'\u7D2F',45783:'\u7D42',45784:'\u7D32',45785:'\u7D31',45786:'\u7F3D',45787:'\u7F9E',45788:'\u7F9A',45789:'\u7FCC',45790:'\u7FCE',45791:'\u7FD2',45792:'\u801C',45793:'\u804A',45794:'\u8046',45795:'\u812F',45796:'\u8116',45797:'\u8123',45798:'\u812B',45799:'\u8129',45800:'\u8130',45801:'\u8124',45802:'\u8202',45803:'\u8235',45804:'\u8237',45805:'\u8236',45806:'\u8239',45807:'\u838E',45808:'\u839E',45809:'\u8398',45810:'\u8378',45811:'\u83A2',45812:'\u8396',45813:'\u83BD',45814:'\u83AB',45815:'\u8392',45816:'\u838A',45817:'\u8393',45818:'\u8389',45819:'\u83A0',45820:'\u8377',45821:'\u837B',45822:'\u837C',45888:'\u8386',45889:'\u83A7',45890:'\u8655',45891:'\u5F6A',45892:'\u86C7',45893:'\u86C0',45894:'\u86B6',45895:'\u86C4',45896:'\u86B5',45897:'\u86C6',45898:'\u86CB',45899:'\u86B1',45900:'\u86AF',45901:'\u86C9',45902:'\u8853',45903:'\u889E',45904:'\u8888',45905:'\u88AB',45906:'\u8892',45907:'\u8896',45908:'\u888D',45909:'\u888B',45910:'\u8993',45911:'\u898F',45912:'\u8A2A',45913:'\u8A1D',45914:'\u8A23',45915:'\u8A25',45916:'\u8A31',45917:'\u8A2D',45918:'\u8A1F',45919:'\u8A1B',45920:'\u8A22',45921:'\u8C49',45922:'\u8C5A',45923:'\u8CA9',45924:'\u8CAC',45925:'\u8CAB',45926:'\u8CA8',45927:'\u8CAA',45928:'\u8CA7',45929:'\u8D67',45930:'\u8D66',45931:'\u8DBE',45932:'\u8DBA',45933:'\u8EDB',45934:'\u8EDF',45935:'\u9019',45936:'\u900D',45937:'\u901A',45938:'\u9017',45939:'\u9023',45940:'\u901F',45941:'\u901D',45942:'\u9010',45943:'\u9015',45944:'\u901E',45945:'\u9020',45946:'\u900F',45947:'\u9022',45948:'\u9016',45949:'\u901B',45950:'\u9014',45985:'\u90E8',45986:'\u90ED',45987:'\u90FD',45988:'\u9157',45989:'\u91CE',45990:'\u91F5',45991:'\u91E6',45992:'\u91E3',45993:'\u91E7',45994:'\u91ED',45995:'\u91E9',45996:'\u9589',45997:'\u966A',45998:'\u9675',45999:'\u9673',46000:'\u9678',46001:'\u9670',46002:'\u9674',46003:'\u9676',46004:'\u9677',46005:'\u966C',46006:'\u96C0',46007:'\u96EA',46008:'\u96E9',46009:'\u7AE0',46010:'\u7ADF',46011:'\u9802',46012:'\u9803',46013:'\u9B5A',46014:'\u9CE5',46015:'\u9E75',46016:'\u9E7F',46017:'\u9EA5',46018:'\u9EBB',46019:'\u50A2',46020:'\u508D',46021:'\u5085',46022:'\u5099',46023:'\u5091',46024:'\u5080',46025:'\u5096',46026:'\u5098',46027:'\u509A',46028:'\u6700',46029:'\u51F1',46030:'\u5272',46031:'\u5274',46032:'\u5275',46033:'\u5269',46034:'\u52DE',46035:'\u52DD',46036:'\u52DB',46037:'\u535A',46038:'\u53A5',46039:'\u557B',46040:'\u5580',46041:'\u55A7',46042:'\u557C',46043:'\u558A',46044:'\u559D',46045:'\u5598',46046:'\u5582',46047:'\u559C',46048:'\u55AA',46049:'\u5594',46050:'\u5587',46051:'\u558B',46052:'\u5583',46053:'\u55B3',46054:'\u55AE',46055:'\u559F',46056:'\u553E',46057:'\u55B2',46058:'\u559A',46059:'\u55BB',46060:'\u55AC',46061:'\u55B1',46062:'\u557E',46063:'\u5589',46064:'\u55AB',46065:'\u5599',46066:'\u570D',46067:'\u582F',46068:'\u582A',46069:'\u5834',46070:'\u5824',46071:'\u5830',46072:'\u5831',46073:'\u5821',46074:'\u581D',46075:'\u5820',46076:'\u58F9',46077:'\u58FA',46078:'\u5960',46144:'\u5A77',46145:'\u5A9A',46146:'\u5A7F',46147:'\u5A92',46148:'\u5A9B',46149:'\u5AA7',46150:'\u5B73',46151:'\u5B71',46152:'\u5BD2',46153:'\u5BCC',46154:'\u5BD3',46155:'\u5BD0',46156:'\u5C0A',46157:'\u5C0B',46158:'\u5C31',46159:'\u5D4C',46160:'\u5D50',46161:'\u5D34',46162:'\u5D47',46163:'\u5DFD',46164:'\u5E45',46165:'\u5E3D',46166:'\u5E40',46167:'\u5E43',46168:'\u5E7E',46169:'\u5ECA',46170:'\u5EC1',46171:'\u5EC2',46172:'\u5EC4',46173:'\u5F3C',46174:'\u5F6D',46175:'\u5FA9',46176:'\u5FAA',46177:'\u5FA8',46178:'\u60D1',46179:'\u60E1',46180:'\u60B2',46181:'\u60B6',46182:'\u60E0',46183:'\u611C',46184:'\u6123',46185:'\u60FA',46186:'\u6115',46187:'\u60F0',46188:'\u60FB',46189:'\u60F4',46190:'\u6168',46191:'\u60F1',46192:'\u610E',46193:'\u60F6',46194:'\u6109',46195:'\u6100',46196:'\u6112',46197:'\u621F',46198:'\u6249',46199:'\u63A3',46200:'\u638C',46201:'\u63CF',46202:'\u63C0',46203:'\u63E9',46204:'\u63C9',46205:'\u63C6',46206:'\u63CD',46241:'\u63D2',46242:'\u63E3',46243:'\u63D0',46244:'\u63E1',46245:'\u63D6',46246:'\u63ED',46247:'\u63EE',46248:'\u6376',46249:'\u63F4',46250:'\u63EA',46251:'\u63DB',46252:'\u6452',46253:'\u63DA',46254:'\u63F9',46255:'\u655E',46256:'\u6566',46257:'\u6562',46258:'\u6563',46259:'\u6591',46260:'\u6590',46261:'\u65AF',46262:'\u666E',46263:'\u6670',46264:'\u6674',46265:'\u6676',46266:'\u666F',46267:'\u6691',46268:'\u667A',46269:'\u667E',46270:'\u6677',46271:'\u66FE',46272:'\u66FF',46273:'\u671F',46274:'\u671D',46275:'\u68FA',46276:'\u68D5',46277:'\u68E0',46278:'\u68D8',46279:'\u68D7',46280:'\u6905',46281:'\u68DF',46282:'\u68F5',46283:'\u68EE',46284:'\u68E7',46285:'\u68F9',46286:'\u68D2',46287:'\u68F2',46288:'\u68E3',46289:'\u68CB',46290:'\u68CD',46291:'\u690D',46292:'\u6912',46293:'\u690E',46294:'\u68C9',46295:'\u68DA',46296:'\u696E',46297:'\u68FB',46298:'\u6B3E',46299:'\u6B3A',46300:'\u6B3D',46301:'\u6B98',46302:'\u6B96',46303:'\u6BBC',46304:'\u6BEF',46305:'\u6C2E',46306:'\u6C2F',46307:'\u6C2C',46308:'\u6E2F',46309:'\u6E38',46310:'\u6E54',46311:'\u6E21',46312:'\u6E32',46313:'\u6E67',46314:'\u6E4A',46315:'\u6E20',46316:'\u6E25',46317:'\u6E23',46318:'\u6E1B',46319:'\u6E5B',46320:'\u6E58',46321:'\u6E24',46322:'\u6E56',46323:'\u6E6E',46324:'\u6E2D',46325:'\u6E26',46326:'\u6E6F',46327:'\u6E34',46328:'\u6E4D',46329:'\u6E3A',46330:'\u6E2C',46331:'\u6E43',46332:'\u6E1D',46333:'\u6E3E',46334:'\u6ECB',46400:'\u6E89',46401:'\u6E19',46402:'\u6E4E',46403:'\u6E63',46404:'\u6E44',46405:'\u6E72',46406:'\u6E69',46407:'\u6E5F',46408:'\u7119',46409:'\u711A',46410:'\u7126',46411:'\u7130',46412:'\u7121',46413:'\u7136',46414:'\u716E',46415:'\u711C',46416:'\u724C',46417:'\u7284',46418:'\u7280',46419:'\u7336',46420:'\u7325',46421:'\u7334',46422:'\u7329',46423:'\u743A',46424:'\u742A',46425:'\u7433',46426:'\u7422',46427:'\u7425',46428:'\u7435',46429:'\u7436',46430:'\u7434',46431:'\u742F',46432:'\u741B',46433:'\u7426',46434:'\u7428',46435:'\u7525',46436:'\u7526',46437:'\u756B',46438:'\u756A',46439:'\u75E2',46440:'\u75DB',46441:'\u75E3',46442:'\u75D9',46443:'\u75D8',46444:'\u75DE',46445:'\u75E0',46446:'\u767B',46447:'\u767C',46448:'\u7696',46449:'\u7693',46450:'\u76B4',46451:'\u76DC',46452:'\u774F',46453:'\u77ED',46454:'\u785D',46455:'\u786C',46456:'\u786F',46457:'\u7A0D',46458:'\u7A08',46459:'\u7A0B',46460:'\u7A05',46461:'\u7A00',46462:'\u7A98',46497:'\u7A97',46498:'\u7A96',46499:'\u7AE5',46500:'\u7AE3',46501:'\u7B49',46502:'\u7B56',46503:'\u7B46',46504:'\u7B50',46505:'\u7B52',46506:'\u7B54',46507:'\u7B4D',46508:'\u7B4B',46509:'\u7B4F',46510:'\u7B51',46511:'\u7C9F',46512:'\u7CA5',46513:'\u7D5E',46514:'\u7D50',46515:'\u7D68',46516:'\u7D55',46517:'\u7D2B',46518:'\u7D6E',46519:'\u7D72',46520:'\u7D61',46521:'\u7D66',46522:'\u7D62',46523:'\u7D70',46524:'\u7D73',46525:'\u5584',46526:'\u7FD4',46527:'\u7FD5',46528:'\u800B',46529:'\u8052',46530:'\u8085',46531:'\u8155',46532:'\u8154',46533:'\u814B',46534:'\u8151',46535:'\u814E',46536:'\u8139',46537:'\u8146',46538:'\u813E',46539:'\u814C',46540:'\u8153',46541:'\u8174',46542:'\u8212',46543:'\u821C',46544:'\u83E9',46545:'\u8403',46546:'\u83F8',46547:'\u840D',46548:'\u83E0',46549:'\u83C5',46550:'\u840B',46551:'\u83C1',46552:'\u83EF',46553:'\u83F1',46554:'\u83F4',46555:'\u8457',46556:'\u840A',46557:'\u83F0',46558:'\u840C',46559:'\u83CC',46560:'\u83FD',46561:'\u83F2',46562:'\u83CA',46563:'\u8438',46564:'\u840E',46565:'\u8404',46566:'\u83DC',46567:'\u8407',46568:'\u83D4',46569:'\u83DF',46570:'\u865B',46571:'\u86DF',46572:'\u86D9',46573:'\u86ED',46574:'\u86D4',46575:'\u86DB',46576:'\u86E4',46577:'\u86D0',46578:'\u86DE',46579:'\u8857',46580:'\u88C1',46581:'\u88C2',46582:'\u88B1',46583:'\u8983',46584:'\u8996',46585:'\u8A3B',46586:'\u8A60',46587:'\u8A55',46588:'\u8A5E',46589:'\u8A3C',46590:'\u8A41',46656:'\u8A54',46657:'\u8A5B',46658:'\u8A50',46659:'\u8A46',46660:'\u8A34',46661:'\u8A3A',46662:'\u8A36',46663:'\u8A56',46664:'\u8C61',46665:'\u8C82',46666:'\u8CAF',46667:'\u8CBC',46668:'\u8CB3',46669:'\u8CBD',46670:'\u8CC1',46671:'\u8CBB',46672:'\u8CC0',46673:'\u8CB4',46674:'\u8CB7',46675:'\u8CB6',46676:'\u8CBF',46677:'\u8CB8',46678:'\u8D8A',46679:'\u8D85',46680:'\u8D81',46681:'\u8DCE',46682:'\u8DDD',46683:'\u8DCB',46684:'\u8DDA',46685:'\u8DD1',46686:'\u8DCC',46687:'\u8DDB',46688:'\u8DC6',46689:'\u8EFB',46690:'\u8EF8',46691:'\u8EFC',46692:'\u8F9C',46693:'\u902E',46694:'\u9035',46695:'\u9031',46696:'\u9038',46697:'\u9032',46698:'\u9036',46699:'\u9102',46700:'\u90F5',46701:'\u9109',46702:'\u90FE',46703:'\u9163',46704:'\u9165',46705:'\u91CF',46706:'\u9214',46707:'\u9215',46708:'\u9223',46709:'\u9209',46710:'\u921E',46711:'\u920D',46712:'\u9210',46713:'\u9207',46714:'\u9211',46715:'\u9594',46716:'\u958F',46717:'\u958B',46718:'\u9591',46753:'\u9593',46754:'\u9592',46755:'\u958E',46756:'\u968A',46757:'\u968E',46758:'\u968B',46759:'\u967D',46760:'\u9685',46761:'\u9686',46762:'\u968D',46763:'\u9672',46764:'\u9684',46765:'\u96C1',46766:'\u96C5',46767:'\u96C4',46768:'\u96C6',46769:'\u96C7',46770:'\u96EF',46771:'\u96F2',46772:'\u97CC',46773:'\u9805',46774:'\u9806',46775:'\u9808',46776:'\u98E7',46777:'\u98EA',46778:'\u98EF',46779:'\u98E9',46780:'\u98F2',46781:'\u98ED',46782:'\u99AE',46783:'\u99AD',46784:'\u9EC3',46785:'\u9ECD',46786:'\u9ED1',46787:'\u4E82',46788:'\u50AD',46789:'\u50B5',46790:'\u50B2',46791:'\u50B3',46792:'\u50C5',46793:'\u50BE',46794:'\u50AC',46795:'\u50B7',46796:'\u50BB',46797:'\u50AF',46798:'\u50C7',46799:'\u527F',46800:'\u5277',46801:'\u527D',46802:'\u52DF',46803:'\u52E6',46804:'\u52E4',46805:'\u52E2',46806:'\u52E3',46807:'\u532F',46808:'\u55DF',46809:'\u55E8',46810:'\u55D3',46811:'\u55E6',46812:'\u55CE',46813:'\u55DC',46814:'\u55C7',46815:'\u55D1',46816:'\u55E3',46817:'\u55E4',46818:'\u55EF',46819:'\u55DA',46820:'\u55E1',46821:'\u55C5',46822:'\u55C6',46823:'\u55E5',46824:'\u55C9',46825:'\u5712',46826:'\u5713',46827:'\u585E',46828:'\u5851',46829:'\u5858',46830:'\u5857',46831:'\u585A',46832:'\u5854',46833:'\u586B',46834:'\u584C',46835:'\u586D',46836:'\u584A',46837:'\u5862',46838:'\u5852',46839:'\u584B',46840:'\u5967',46841:'\u5AC1',46842:'\u5AC9',46843:'\u5ACC',46844:'\u5ABE',46845:'\u5ABD',46846:'\u5ABC',46912:'\u5AB3',46913:'\u5AC2',46914:'\u5AB2',46915:'\u5D69',46916:'\u5D6F',46917:'\u5E4C',46918:'\u5E79',46919:'\u5EC9',46920:'\u5EC8',46921:'\u5F12',46922:'\u5F59',46923:'\u5FAC',46924:'\u5FAE',46925:'\u611A',46926:'\u610F',46927:'\u6148',46928:'\u611F',46929:'\u60F3',46930:'\u611B',46931:'\u60F9',46932:'\u6101',46933:'\u6108',46934:'\u614E',46935:'\u614C',46936:'\u6144',46937:'\u614D',46938:'\u613E',46939:'\u6134',46940:'\u6127',46941:'\u610D',46942:'\u6106',46943:'\u6137',46944:'\u6221',46945:'\u6222',46946:'\u6413',46947:'\u643E',46948:'\u641E',46949:'\u642A',46950:'\u642D',46951:'\u643D',46952:'\u642C',46953:'\u640F',46954:'\u641C',46955:'\u6414',46956:'\u640D',46957:'\u6436',46958:'\u6416',46959:'\u6417',46960:'\u6406',46961:'\u656C',46962:'\u659F',46963:'\u65B0',46964:'\u6697',46965:'\u6689',46966:'\u6687',46967:'\u6688',46968:'\u6696',46969:'\u6684',46970:'\u6698',46971:'\u668D',46972:'\u6703',46973:'\u6994',46974:'\u696D',47009:'\u695A',47010:'\u6977',47011:'\u6960',47012:'\u6954',47013:'\u6975',47014:'\u6930',47015:'\u6982',47016:'\u694A',47017:'\u6968',47018:'\u696B',47019:'\u695E',47020:'\u6953',47021:'\u6979',47022:'\u6986',47023:'\u695D',47024:'\u6963',47025:'\u695B',47026:'\u6B47',47027:'\u6B72',47028:'\u6BC0',47029:'\u6BBF',47030:'\u6BD3',47031:'\u6BFD',47032:'\u6EA2',47033:'\u6EAF',47034:'\u6ED3',47035:'\u6EB6',47036:'\u6EC2',47037:'\u6E90',47038:'\u6E9D',47039:'\u6EC7',47040:'\u6EC5',47041:'\u6EA5',47042:'\u6E98',47043:'\u6EBC',47044:'\u6EBA',47045:'\u6EAB',47046:'\u6ED1',47047:'\u6E96',47048:'\u6E9C',47049:'\u6EC4',47050:'\u6ED4',47051:'\u6EAA',47052:'\u6EA7',47053:'\u6EB4',47054:'\u714E',47055:'\u7159',47056:'\u7169',47057:'\u7164',47058:'\u7149',47059:'\u7167',47060:'\u715C',47061:'\u716C',47062:'\u7166',47063:'\u714C',47064:'\u7165',47065:'\u715E',47066:'\u7146',47067:'\u7168',47068:'\u7156',47069:'\u723A',47070:'\u7252',47071:'\u7337',47072:'\u7345',47073:'\u733F',47074:'\u733E',47075:'\u746F',47076:'\u745A',47077:'\u7455',47078:'\u745F',47079:'\u745E',47080:'\u7441',47081:'\u743F',47082:'\u7459',47083:'\u745B',47084:'\u745C',47085:'\u7576',47086:'\u7578',47087:'\u7600',47088:'\u75F0',47089:'\u7601',47090:'\u75F2',47091:'\u75F1',47092:'\u75FA',47093:'\u75FF',47094:'\u75F4',47095:'\u75F3',47096:'\u76DE',47097:'\u76DF',47098:'\u775B',47099:'\u776B',47100:'\u7766',47101:'\u775E',47102:'\u7763',47168:'\u7779',47169:'\u776A',47170:'\u776C',47171:'\u775C',47172:'\u7765',47173:'\u7768',47174:'\u7762',47175:'\u77EE',47176:'\u788E',47177:'\u78B0',47178:'\u7897',47179:'\u7898',47180:'\u788C',47181:'\u7889',47182:'\u787C',47183:'\u7891',47184:'\u7893',47185:'\u787F',47186:'\u797A',47187:'\u797F',47188:'\u7981',47189:'\u842C',47190:'\u79BD',47191:'\u7A1C',47192:'\u7A1A',47193:'\u7A20',47194:'\u7A14',47195:'\u7A1F',47196:'\u7A1E',47197:'\u7A9F',47198:'\u7AA0',47199:'\u7B77',47200:'\u7BC0',47201:'\u7B60',47202:'\u7B6E',47203:'\u7B67',47204:'\u7CB1',47205:'\u7CB3',47206:'\u7CB5',47207:'\u7D93',47208:'\u7D79',47209:'\u7D91',47210:'\u7D81',47211:'\u7D8F',47212:'\u7D5B',47213:'\u7F6E',47214:'\u7F69',47215:'\u7F6A',47216:'\u7F72',47217:'\u7FA9',47218:'\u7FA8',47219:'\u7FA4',47220:'\u8056',47221:'\u8058',47222:'\u8086',47223:'\u8084',47224:'\u8171',47225:'\u8170',47226:'\u8178',47227:'\u8165',47228:'\u816E',47229:'\u8173',47230:'\u816B',47265:'\u8179',47266:'\u817A',47267:'\u8166',47268:'\u8205',47269:'\u8247',47270:'\u8482',47271:'\u8477',47272:'\u843D',47273:'\u8431',47274:'\u8475',47275:'\u8466',47276:'\u846B',47277:'\u8449',47278:'\u846C',47279:'\u845B',47280:'\u843C',47281:'\u8435',47282:'\u8461',47283:'\u8463',47284:'\u8469',47285:'\u846D',47286:'\u8446',47287:'\u865E',47288:'\u865C',47289:'\u865F',47290:'\u86F9',47291:'\u8713',47292:'\u8708',47293:'\u8707',47294:'\u8700',47295:'\u86FE',47296:'\u86FB',47297:'\u8702',47298:'\u8703',47299:'\u8706',47300:'\u870A',47301:'\u8859',47302:'\u88DF',47303:'\u88D4',47304:'\u88D9',47305:'\u88DC',47306:'\u88D8',47307:'\u88DD',47308:'\u88E1',47309:'\u88CA',47310:'\u88D5',47311:'\u88D2',47312:'\u899C',47313:'\u89E3',47314:'\u8A6B',47315:'\u8A72',47316:'\u8A73',47317:'\u8A66',47318:'\u8A69',47319:'\u8A70',47320:'\u8A87',47321:'\u8A7C',47322:'\u8A63',47323:'\u8AA0',47324:'\u8A71',47325:'\u8A85',47326:'\u8A6D',47327:'\u8A62',47328:'\u8A6E',47329:'\u8A6C',47330:'\u8A79',47331:'\u8A7B',47332:'\u8A3E',47333:'\u8A68',47334:'\u8C62',47335:'\u8C8A',47336:'\u8C89',47337:'\u8CCA',47338:'\u8CC7',47339:'\u8CC8',47340:'\u8CC4',47341:'\u8CB2',47342:'\u8CC3',47343:'\u8CC2',47344:'\u8CC5',47345:'\u8DE1',47346:'\u8DDF',47347:'\u8DE8',47348:'\u8DEF',47349:'\u8DF3',47350:'\u8DFA',47351:'\u8DEA',47352:'\u8DE4',47353:'\u8DE6',47354:'\u8EB2',47355:'\u8F03',47356:'\u8F09',47357:'\u8EFE',47358:'\u8F0A',47424:'\u8F9F',47425:'\u8FB2',47426:'\u904B',47427:'\u904A',47428:'\u9053',47429:'\u9042',47430:'\u9054',47431:'\u903C',47432:'\u9055',47433:'\u9050',47434:'\u9047',47435:'\u904F',47436:'\u904E',47437:'\u904D',47438:'\u9051',47439:'\u903E',47440:'\u9041',47441:'\u9112',47442:'\u9117',47443:'\u916C',47444:'\u916A',47445:'\u9169',47446:'\u91C9',47447:'\u9237',47448:'\u9257',47449:'\u9238',47450:'\u923D',47451:'\u9240',47452:'\u923E',47453:'\u925B',47454:'\u924B',47455:'\u9264',47456:'\u9251',47457:'\u9234',47458:'\u9249',47459:'\u924D',47460:'\u9245',47461:'\u9239',47462:'\u923F',47463:'\u925A',47464:'\u9598',47465:'\u9698',47466:'\u9694',47467:'\u9695',47468:'\u96CD',47469:'\u96CB',47470:'\u96C9',47471:'\u96CA',47472:'\u96F7',47473:'\u96FB',47474:'\u96F9',47475:'\u96F6',47476:'\u9756',47477:'\u9774',47478:'\u9776',47479:'\u9810',47480:'\u9811',47481:'\u9813',47482:'\u980A',47483:'\u9812',47484:'\u980C',47485:'\u98FC',47486:'\u98F4',47521:'\u98FD',47522:'\u98FE',47523:'\u99B3',47524:'\u99B1',47525:'\u99B4',47526:'\u9AE1',47527:'\u9CE9',47528:'\u9E82',47529:'\u9F0E',47530:'\u9F13',47531:'\u9F20',47532:'\u50E7',47533:'\u50EE',47534:'\u50E5',47535:'\u50D6',47536:'\u50ED',47537:'\u50DA',47538:'\u50D5',47539:'\u50CF',47540:'\u50D1',47541:'\u50F1',47542:'\u50CE',47543:'\u50E9',47544:'\u5162',47545:'\u51F3',47546:'\u5283',47547:'\u5282',47548:'\u5331',47549:'\u53AD',47550:'\u55FE',47551:'\u5600',47552:'\u561B',47553:'\u5617',47554:'\u55FD',47555:'\u5614',47556:'\u5606',47557:'\u5609',47558:'\u560D',47559:'\u560E',47560:'\u55F7',47561:'\u5616',47562:'\u561F',47563:'\u5608',47564:'\u5610',47565:'\u55F6',47566:'\u5718',47567:'\u5716',47568:'\u5875',47569:'\u587E',47570:'\u5883',47571:'\u5893',47572:'\u588A',47573:'\u5879',47574:'\u5885',47575:'\u587D',47576:'\u58FD',47577:'\u5925',47578:'\u5922',47579:'\u5924',47580:'\u596A',47581:'\u5969',47582:'\u5AE1',47583:'\u5AE6',47584:'\u5AE9',47585:'\u5AD7',47586:'\u5AD6',47587:'\u5AD8',47588:'\u5AE3',47589:'\u5B75',47590:'\u5BDE',47591:'\u5BE7',47592:'\u5BE1',47593:'\u5BE5',47594:'\u5BE6',47595:'\u5BE8',47596:'\u5BE2',47597:'\u5BE4',47598:'\u5BDF',47599:'\u5C0D',47600:'\u5C62',47601:'\u5D84',47602:'\u5D87',47603:'\u5E5B',47604:'\u5E63',47605:'\u5E55',47606:'\u5E57',47607:'\u5E54',47608:'\u5ED3',47609:'\u5ED6',47610:'\u5F0A',47611:'\u5F46',47612:'\u5F70',47613:'\u5FB9',47614:'\u6147',47680:'\u613F',47681:'\u614B',47682:'\u6177',47683:'\u6162',47684:'\u6163',47685:'\u615F',47686:'\u615A',47687:'\u6158',47688:'\u6175',47689:'\u622A',47690:'\u6487',47691:'\u6458',47692:'\u6454',47693:'\u64A4',47694:'\u6478',47695:'\u645F',47696:'\u647A',47697:'\u6451',47698:'\u6467',47699:'\u6434',47700:'\u646D',47701:'\u647B',47702:'\u6572',47703:'\u65A1',47704:'\u65D7',47705:'\u65D6',47706:'\u66A2',47707:'\u66A8',47708:'\u669D',47709:'\u699C',47710:'\u69A8',47711:'\u6995',47712:'\u69C1',47713:'\u69AE',47714:'\u69D3',47715:'\u69CB',47716:'\u699B',47717:'\u69B7',47718:'\u69BB',47719:'\u69AB',47720:'\u69B4',47721:'\u69D0',47722:'\u69CD',47723:'\u69AD',47724:'\u69CC',47725:'\u69A6',47726:'\u69C3',47727:'\u69A3',47728:'\u6B49',47729:'\u6B4C',47730:'\u6C33',47731:'\u6F33',47732:'\u6F14',47733:'\u6EFE',47734:'\u6F13',47735:'\u6EF4',47736:'\u6F29',47737:'\u6F3E',47738:'\u6F20',47739:'\u6F2C',47740:'\u6F0F',47741:'\u6F02',47742:'\u6F22',47777:'\u6EFF',47778:'\u6EEF',47779:'\u6F06',47780:'\u6F31',47781:'\u6F38',47782:'\u6F32',47783:'\u6F23',47784:'\u6F15',47785:'\u6F2B',47786:'\u6F2F',47787:'\u6F88',47788:'\u6F2A',47789:'\u6EEC',47790:'\u6F01',47791:'\u6EF2',47792:'\u6ECC',47793:'\u6EF7',47794:'\u7194',47795:'\u7199',47796:'\u717D',47797:'\u718A',47798:'\u7184',47799:'\u7192',47800:'\u723E',47801:'\u7292',47802:'\u7296',47803:'\u7344',47804:'\u7350',47805:'\u7464',47806:'\u7463',47807:'\u746A',47808:'\u7470',47809:'\u746D',47810:'\u7504',47811:'\u7591',47812:'\u7627',47813:'\u760D',47814:'\u760B',47815:'\u7609',47816:'\u7613',47817:'\u76E1',47818:'\u76E3',47819:'\u7784',47820:'\u777D',47821:'\u777F',47822:'\u7761',47823:'\u78C1',47824:'\u789F',47825:'\u78A7',47826:'\u78B3',47827:'\u78A9',47828:'\u78A3',47829:'\u798E',47830:'\u798F',47831:'\u798D',47832:'\u7A2E',47833:'\u7A31',47834:'\u7AAA',47835:'\u7AA9',47836:'\u7AED',47837:'\u7AEF',47838:'\u7BA1',47839:'\u7B95',47840:'\u7B8B',47841:'\u7B75',47842:'\u7B97',47843:'\u7B9D',47844:'\u7B94',47845:'\u7B8F',47846:'\u7BB8',47847:'\u7B87',47848:'\u7B84',47849:'\u7CB9',47850:'\u7CBD',47851:'\u7CBE',47852:'\u7DBB',47853:'\u7DB0',47854:'\u7D9C',47855:'\u7DBD',47856:'\u7DBE',47857:'\u7DA0',47858:'\u7DCA',47859:'\u7DB4',47860:'\u7DB2',47861:'\u7DB1',47862:'\u7DBA',47863:'\u7DA2',47864:'\u7DBF',47865:'\u7DB5',47866:'\u7DB8',47867:'\u7DAD',47868:'\u7DD2',47869:'\u7DC7',47870:'\u7DAC',47936:'\u7F70',47937:'\u7FE0',47938:'\u7FE1',47939:'\u7FDF',47940:'\u805E',47941:'\u805A',47942:'\u8087',47943:'\u8150',47944:'\u8180',47945:'\u818F',47946:'\u8188',47947:'\u818A',47948:'\u817F',47949:'\u8182',47950:'\u81E7',47951:'\u81FA',47952:'\u8207',47953:'\u8214',47954:'\u821E',47955:'\u824B',47956:'\u84C9',47957:'\u84BF',47958:'\u84C6',47959:'\u84C4',47960:'\u8499',47961:'\u849E',47962:'\u84B2',47963:'\u849C',47964:'\u84CB',47965:'\u84B8',47966:'\u84C0',47967:'\u84D3',47968:'\u8490',47969:'\u84BC',47970:'\u84D1',47971:'\u84CA',47972:'\u873F',47973:'\u871C',47974:'\u873B',47975:'\u8722',47976:'\u8725',47977:'\u8734',47978:'\u8718',47979:'\u8755',47980:'\u8737',47981:'\u8729',47982:'\u88F3',47983:'\u8902',47984:'\u88F4',47985:'\u88F9',47986:'\u88F8',47987:'\u88FD',47988:'\u88E8',47989:'\u891A',47990:'\u88EF',47991:'\u8AA6',47992:'\u8A8C',47993:'\u8A9E',47994:'\u8AA3',47995:'\u8A8D',47996:'\u8AA1',47997:'\u8A93',47998:'\u8AA4',48033:'\u8AAA',48034:'\u8AA5',48035:'\u8AA8',48036:'\u8A98',48037:'\u8A91',48038:'\u8A9A',48039:'\u8AA7',48040:'\u8C6A',48041:'\u8C8D',48042:'\u8C8C',48043:'\u8CD3',48044:'\u8CD1',48045:'\u8CD2',48046:'\u8D6B',48047:'\u8D99',48048:'\u8D95',48049:'\u8DFC',48050:'\u8F14',48051:'\u8F12',48052:'\u8F15',48053:'\u8F13',48054:'\u8FA3',48055:'\u9060',48056:'\u9058',48057:'\u905C',48058:'\u9063',48059:'\u9059',48060:'\u905E',48061:'\u9062',48062:'\u905D',48063:'\u905B',48064:'\u9119',48065:'\u9118',48066:'\u911E',48067:'\u9175',48068:'\u9178',48069:'\u9177',48070:'\u9174',48071:'\u9278',48072:'\u9280',48073:'\u9285',48074:'\u9298',48075:'\u9296',48076:'\u927B',48077:'\u9293',48078:'\u929C',48079:'\u92A8',48080:'\u927C',48081:'\u9291',48082:'\u95A1',48083:'\u95A8',48084:'\u95A9',48085:'\u95A3',48086:'\u95A5',48087:'\u95A4',48088:'\u9699',48089:'\u969C',48090:'\u969B',48091:'\u96CC',48092:'\u96D2',48093:'\u9700',48094:'\u977C',48095:'\u9785',48096:'\u97F6',48097:'\u9817',48098:'\u9818',48099:'\u98AF',48100:'\u98B1',48101:'\u9903',48102:'\u9905',48103:'\u990C',48104:'\u9909',48105:'\u99C1',48106:'\u9AAF',48107:'\u9AB0',48108:'\u9AE6',48109:'\u9B41',48110:'\u9B42',48111:'\u9CF4',48112:'\u9CF6',48113:'\u9CF3',48114:'\u9EBC',48115:'\u9F3B',48116:'\u9F4A',48117:'\u5104',48118:'\u5100',48119:'\u50FB',48120:'\u50F5',48121:'\u50F9',48122:'\u5102',48123:'\u5108',48124:'\u5109',48125:'\u5105',48126:'\u51DC',48192:'\u5287',48193:'\u5288',48194:'\u5289',48195:'\u528D',48196:'\u528A',48197:'\u52F0',48198:'\u53B2',48199:'\u562E',48200:'\u563B',48201:'\u5639',48202:'\u5632',48203:'\u563F',48204:'\u5634',48205:'\u5629',48206:'\u5653',48207:'\u564E',48208:'\u5657',48209:'\u5674',48210:'\u5636',48211:'\u562F',48212:'\u5630',48213:'\u5880',48214:'\u589F',48215:'\u589E',48216:'\u58B3',48217:'\u589C',48218:'\u58AE',48219:'\u58A9',48220:'\u58A6',48221:'\u596D',48222:'\u5B09',48223:'\u5AFB',48224:'\u5B0B',48225:'\u5AF5',48226:'\u5B0C',48227:'\u5B08',48228:'\u5BEE',48229:'\u5BEC',48230:'\u5BE9',48231:'\u5BEB',48232:'\u5C64',48233:'\u5C65',48234:'\u5D9D',48235:'\u5D94',48236:'\u5E62',48237:'\u5E5F',48238:'\u5E61',48239:'\u5EE2',48240:'\u5EDA',48241:'\u5EDF',48242:'\u5EDD',48243:'\u5EE3',48244:'\u5EE0',48245:'\u5F48',48246:'\u5F71',48247:'\u5FB7',48248:'\u5FB5',48249:'\u6176',48250:'\u6167',48251:'\u616E',48252:'\u615D',48253:'\u6155',48254:'\u6182',48289:'\u617C',48290:'\u6170',48291:'\u616B',48292:'\u617E',48293:'\u61A7',48294:'\u6190',48295:'\u61AB',48296:'\u618E',48297:'\u61AC',48298:'\u619A',48299:'\u61A4',48300:'\u6194',48301:'\u61AE',48302:'\u622E',48303:'\u6469',48304:'\u646F',48305:'\u6479',48306:'\u649E',48307:'\u64B2',48308:'\u6488',48309:'\u6490',48310:'\u64B0',48311:'\u64A5',48312:'\u6493',48313:'\u6495',48314:'\u64A9',48315:'\u6492',48316:'\u64AE',48317:'\u64AD',48318:'\u64AB',48319:'\u649A',48320:'\u64AC',48321:'\u6499',48322:'\u64A2',48323:'\u64B3',48324:'\u6575',48325:'\u6577',48326:'\u6578',48327:'\u66AE',48328:'\u66AB',48329:'\u66B4',48330:'\u66B1',48331:'\u6A23',48332:'\u6A1F',48333:'\u69E8',48334:'\u6A01',48335:'\u6A1E',48336:'\u6A19',48337:'\u69FD',48338:'\u6A21',48339:'\u6A13',48340:'\u6A0A',48341:'\u69F3',48342:'\u6A02',48343:'\u6A05',48344:'\u69ED',48345:'\u6A11',48346:'\u6B50',48347:'\u6B4E',48348:'\u6BA4',48349:'\u6BC5',48350:'\u6BC6',48351:'\u6F3F',48352:'\u6F7C',48353:'\u6F84',48354:'\u6F51',48355:'\u6F66',48356:'\u6F54',48357:'\u6F86',48358:'\u6F6D',48359:'\u6F5B',48360:'\u6F78',48361:'\u6F6E',48362:'\u6F8E',48363:'\u6F7A',48364:'\u6F70',48365:'\u6F64',48366:'\u6F97',48367:'\u6F58',48368:'\u6ED5',48369:'\u6F6F',48370:'\u6F60',48371:'\u6F5F',48372:'\u719F',48373:'\u71AC',48374:'\u71B1',48375:'\u71A8',48376:'\u7256',48377:'\u729B',48378:'\u734E',48379:'\u7357',48380:'\u7469',48381:'\u748B',48382:'\u7483',48448:'\u747E',48449:'\u7480',48450:'\u757F',48451:'\u7620',48452:'\u7629',48453:'\u761F',48454:'\u7624',48455:'\u7626',48456:'\u7621',48457:'\u7622',48458:'\u769A',48459:'\u76BA',48460:'\u76E4',48461:'\u778E',48462:'\u7787',48463:'\u778C',48464:'\u7791',48465:'\u778B',48466:'\u78CB',48467:'\u78C5',48468:'\u78BA',48469:'\u78CA',48470:'\u78BE',48471:'\u78D5',48472:'\u78BC',48473:'\u78D0',48474:'\u7A3F',48475:'\u7A3C',48476:'\u7A40',48477:'\u7A3D',48478:'\u7A37',48479:'\u7A3B',48480:'\u7AAF',48481:'\u7AAE',48482:'\u7BAD',48483:'\u7BB1',48484:'\u7BC4',48485:'\u7BB4',48486:'\u7BC6',48487:'\u7BC7',48488:'\u7BC1',48489:'\u7BA0',48490:'\u7BCC',48491:'\u7CCA',48492:'\u7DE0',48493:'\u7DF4',48494:'\u7DEF',48495:'\u7DFB',48496:'\u7DD8',48497:'\u7DEC',48498:'\u7DDD',48499:'\u7DE8',48500:'\u7DE3',48501:'\u7DDA',48502:'\u7DDE',48503:'\u7DE9',48504:'\u7D9E',48505:'\u7DD9',48506:'\u7DF2',48507:'\u7DF9',48508:'\u7F75',48509:'\u7F77',48510:'\u7FAF',48545:'\u7FE9',48546:'\u8026',48547:'\u819B',48548:'\u819C',48549:'\u819D',48550:'\u81A0',48551:'\u819A',48552:'\u8198',48553:'\u8517',48554:'\u853D',48555:'\u851A',48556:'\u84EE',48557:'\u852C',48558:'\u852D',48559:'\u8513',48560:'\u8511',48561:'\u8523',48562:'\u8521',48563:'\u8514',48564:'\u84EC',48565:'\u8525',48566:'\u84FF',48567:'\u8506',48568:'\u8782',48569:'\u8774',48570:'\u8776',48571:'\u8760',48572:'\u8766',48573:'\u8778',48574:'\u8768',48575:'\u8759',48576:'\u8757',48577:'\u874C',48578:'\u8753',48579:'\u885B',48580:'\u885D',48581:'\u8910',48582:'\u8907',48583:'\u8912',48584:'\u8913',48585:'\u8915',48586:'\u890A',48587:'\u8ABC',48588:'\u8AD2',48589:'\u8AC7',48590:'\u8AC4',48591:'\u8A95',48592:'\u8ACB',48593:'\u8AF8',48594:'\u8AB2',48595:'\u8AC9',48596:'\u8AC2',48597:'\u8ABF',48598:'\u8AB0',48599:'\u8AD6',48600:'\u8ACD',48601:'\u8AB6',48602:'\u8AB9',48603:'\u8ADB',48604:'\u8C4C',48605:'\u8C4E',48606:'\u8C6C',48607:'\u8CE0',48608:'\u8CDE',48609:'\u8CE6',48610:'\u8CE4',48611:'\u8CEC',48612:'\u8CED',48613:'\u8CE2',48614:'\u8CE3',48615:'\u8CDC',48616:'\u8CEA',48617:'\u8CE1',48618:'\u8D6D',48619:'\u8D9F',48620:'\u8DA3',48621:'\u8E2B',48622:'\u8E10',48623:'\u8E1D',48624:'\u8E22',48625:'\u8E0F',48626:'\u8E29',48627:'\u8E1F',48628:'\u8E21',48629:'\u8E1E',48630:'\u8EBA',48631:'\u8F1D',48632:'\u8F1B',48633:'\u8F1F',48634:'\u8F29',48635:'\u8F26',48636:'\u8F2A',48637:'\u8F1C',48638:'\u8F1E',48704:'\u8F25',48705:'\u9069',48706:'\u906E',48707:'\u9068',48708:'\u906D',48709:'\u9077',48710:'\u9130',48711:'\u912D',48712:'\u9127',48713:'\u9131',48714:'\u9187',48715:'\u9189',48716:'\u918B',48717:'\u9183',48718:'\u92C5',48719:'\u92BB',48720:'\u92B7',48721:'\u92EA',48722:'\u92AC',48723:'\u92E4',48724:'\u92C1',48725:'\u92B3',48726:'\u92BC',48727:'\u92D2',48728:'\u92C7',48729:'\u92F0',48730:'\u92B2',48731:'\u95AD',48732:'\u95B1',48733:'\u9704',48734:'\u9706',48735:'\u9707',48736:'\u9709',48737:'\u9760',48738:'\u978D',48739:'\u978B',48740:'\u978F',48741:'\u9821',48742:'\u982B',48743:'\u981C',48744:'\u98B3',48745:'\u990A',48746:'\u9913',48747:'\u9912',48748:'\u9918',48749:'\u99DD',48750:'\u99D0',48751:'\u99DF',48752:'\u99DB',48753:'\u99D1',48754:'\u99D5',48755:'\u99D2',48756:'\u99D9',48757:'\u9AB7',48758:'\u9AEE',48759:'\u9AEF',48760:'\u9B27',48761:'\u9B45',48762:'\u9B44',48763:'\u9B77',48764:'\u9B6F',48765:'\u9D06',48766:'\u9D09',48801:'\u9D03',48802:'\u9EA9',48803:'\u9EBE',48804:'\u9ECE',48805:'\u58A8',48806:'\u9F52',48807:'\u5112',48808:'\u5118',48809:'\u5114',48810:'\u5110',48811:'\u5115',48812:'\u5180',48813:'\u51AA',48814:'\u51DD',48815:'\u5291',48816:'\u5293',48817:'\u52F3',48818:'\u5659',48819:'\u566B',48820:'\u5679',48821:'\u5669',48822:'\u5664',48823:'\u5678',48824:'\u566A',48825:'\u5668',48826:'\u5665',48827:'\u5671',48828:'\u566F',48829:'\u566C',48830:'\u5662',48831:'\u5676',48832:'\u58C1',48833:'\u58BE',48834:'\u58C7',48835:'\u58C5',48836:'\u596E',48837:'\u5B1D',48838:'\u5B34',48839:'\u5B78',48840:'\u5BF0',48841:'\u5C0E',48842:'\u5F4A',48843:'\u61B2',48844:'\u6191',48845:'\u61A9',48846:'\u618A',48847:'\u61CD',48848:'\u61B6',48849:'\u61BE',48850:'\u61CA',48851:'\u61C8',48852:'\u6230',48853:'\u64C5',48854:'\u64C1',48855:'\u64CB',48856:'\u64BB',48857:'\u64BC',48858:'\u64DA',48859:'\u64C4',48860:'\u64C7',48861:'\u64C2',48862:'\u64CD',48863:'\u64BF',48864:'\u64D2',48865:'\u64D4',48866:'\u64BE',48867:'\u6574',48868:'\u66C6',48869:'\u66C9',48870:'\u66B9',48871:'\u66C4',48872:'\u66C7',48873:'\u66B8',48874:'\u6A3D',48875:'\u6A38',48876:'\u6A3A',48877:'\u6A59',48878:'\u6A6B',48879:'\u6A58',48880:'\u6A39',48881:'\u6A44',48882:'\u6A62',48883:'\u6A61',48884:'\u6A4B',48885:'\u6A47',48886:'\u6A35',48887:'\u6A5F',48888:'\u6A48',48889:'\u6B59',48890:'\u6B77',48891:'\u6C05',48892:'\u6FC2',48893:'\u6FB1',48894:'\u6FA1',48960:'\u6FC3',48961:'\u6FA4',48962:'\u6FC1',48963:'\u6FA7',48964:'\u6FB3',48965:'\u6FC0',48966:'\u6FB9',48967:'\u6FB6',48968:'\u6FA6',48969:'\u6FA0',48970:'\u6FB4',48971:'\u71BE',48972:'\u71C9',48973:'\u71D0',48974:'\u71D2',48975:'\u71C8',48976:'\u71D5',48977:'\u71B9',48978:'\u71CE',48979:'\u71D9',48980:'\u71DC',48981:'\u71C3',48982:'\u71C4',48983:'\u7368',48984:'\u749C',48985:'\u74A3',48986:'\u7498',48987:'\u749F',48988:'\u749E',48989:'\u74E2',48990:'\u750C',48991:'\u750D',48992:'\u7634',48993:'\u7638',48994:'\u763A',48995:'\u76E7',48996:'\u76E5',48997:'\u77A0',48998:'\u779E',48999:'\u779F',49000:'\u77A5',49001:'\u78E8',49002:'\u78DA',49003:'\u78EC',49004:'\u78E7',49005:'\u79A6',49006:'\u7A4D',49007:'\u7A4E',49008:'\u7A46',49009:'\u7A4C',49010:'\u7A4B',49011:'\u7ABA',49012:'\u7BD9',49013:'\u7C11',49014:'\u7BC9',49015:'\u7BE4',49016:'\u7BDB',49017:'\u7BE1',49018:'\u7BE9',49019:'\u7BE6',49020:'\u7CD5',49021:'\u7CD6',49022:'\u7E0A',49057:'\u7E11',49058:'\u7E08',49059:'\u7E1B',49060:'\u7E23',49061:'\u7E1E',49062:'\u7E1D',49063:'\u7E09',49064:'\u7E10',49065:'\u7F79',49066:'\u7FB2',49067:'\u7FF0',49068:'\u7FF1',49069:'\u7FEE',49070:'\u8028',49071:'\u81B3',49072:'\u81A9',49073:'\u81A8',49074:'\u81FB',49075:'\u8208',49076:'\u8258',49077:'\u8259',49078:'\u854A',49079:'\u8559',49080:'\u8548',49081:'\u8568',49082:'\u8569',49083:'\u8543',49084:'\u8549',49085:'\u856D',49086:'\u856A',49087:'\u855E',49088:'\u8783',49089:'\u879F',49090:'\u879E',49091:'\u87A2',49092:'\u878D',49093:'\u8861',49094:'\u892A',49095:'\u8932',49096:'\u8925',49097:'\u892B',49098:'\u8921',49099:'\u89AA',49100:'\u89A6',49101:'\u8AE6',49102:'\u8AFA',49103:'\u8AEB',49104:'\u8AF1',49105:'\u8B00',49106:'\u8ADC',49107:'\u8AE7',49108:'\u8AEE',49109:'\u8AFE',49110:'\u8B01',49111:'\u8B02',49112:'\u8AF7',49113:'\u8AED',49114:'\u8AF3',49115:'\u8AF6',49116:'\u8AFC',49117:'\u8C6B',49118:'\u8C6D',49119:'\u8C93',49120:'\u8CF4',49121:'\u8E44',49122:'\u8E31',49123:'\u8E34',49124:'\u8E42',49125:'\u8E39',49126:'\u8E35',49127:'\u8F3B',49128:'\u8F2F',49129:'\u8F38',49130:'\u8F33',49131:'\u8FA8',49132:'\u8FA6',49133:'\u9075',49134:'\u9074',49135:'\u9078',49136:'\u9072',49137:'\u907C',49138:'\u907A',49139:'\u9134',49140:'\u9192',49141:'\u9320',49142:'\u9336',49143:'\u92F8',49144:'\u9333',49145:'\u932F',49146:'\u9322',49147:'\u92FC',49148:'\u932B',49149:'\u9304',49150:'\u931A',49216:'\u9310',49217:'\u9326',49218:'\u9321',49219:'\u9315',49220:'\u932E',49221:'\u9319',49222:'\u95BB',49223:'\u96A7',49224:'\u96A8',49225:'\u96AA',49226:'\u96D5',49227:'\u970E',49228:'\u9711',49229:'\u9716',49230:'\u970D',49231:'\u9713',49232:'\u970F',49233:'\u975B',49234:'\u975C',49235:'\u9766',49236:'\u9798',49237:'\u9830',49238:'\u9838',49239:'\u983B',49240:'\u9837',49241:'\u982D',49242:'\u9839',49243:'\u9824',49244:'\u9910',49245:'\u9928',49246:'\u991E',49247:'\u991B',49248:'\u9921',49249:'\u991A',49250:'\u99ED',49251:'\u99E2',49252:'\u99F1',49253:'\u9AB8',49254:'\u9ABC',49255:'\u9AFB',49256:'\u9AED',49257:'\u9B28',49258:'\u9B91',49259:'\u9D15',49260:'\u9D23',49261:'\u9D26',49262:'\u9D28',49263:'\u9D12',49264:'\u9D1B',49265:'\u9ED8',49266:'\u9ED4',49267:'\u9F8D',49268:'\u9F9C',49269:'\u512A',49270:'\u511F',49271:'\u5121',49272:'\u5132',49273:'\u52F5',49274:'\u568E',49275:'\u5680',49276:'\u5690',49277:'\u5685',49278:'\u5687',49313:'\u568F',49314:'\u58D5',49315:'\u58D3',49316:'\u58D1',49317:'\u58CE',49318:'\u5B30',49319:'\u5B2A',49320:'\u5B24',49321:'\u5B7A',49322:'\u5C37',49323:'\u5C68',49324:'\u5DBC',49325:'\u5DBA',49326:'\u5DBD',49327:'\u5DB8',49328:'\u5E6B',49329:'\u5F4C',49330:'\u5FBD',49331:'\u61C9',49332:'\u61C2',49333:'\u61C7',49334:'\u61E6',49335:'\u61CB',49336:'\u6232',49337:'\u6234',49338:'\u64CE',49339:'\u64CA',49340:'\u64D8',49341:'\u64E0',49342:'\u64F0',49343:'\u64E6',49344:'\u64EC',49345:'\u64F1',49346:'\u64E2',49347:'\u64ED',49348:'\u6582',49349:'\u6583',49350:'\u66D9',49351:'\u66D6',49352:'\u6A80',49353:'\u6A94',49354:'\u6A84',49355:'\u6AA2',49356:'\u6A9C',49357:'\u6ADB',49358:'\u6AA3',49359:'\u6A7E',49360:'\u6A97',49361:'\u6A90',49362:'\u6AA0',49363:'\u6B5C',49364:'\u6BAE',49365:'\u6BDA',49366:'\u6C08',49367:'\u6FD8',49368:'\u6FF1',49369:'\u6FDF',49370:'\u6FE0',49371:'\u6FDB',49372:'\u6FE4',49373:'\u6FEB',49374:'\u6FEF',49375:'\u6F80',49376:'\u6FEC',49377:'\u6FE1',49378:'\u6FE9',49379:'\u6FD5',49380:'\u6FEE',49381:'\u6FF0',49382:'\u71E7',49383:'\u71DF',49384:'\u71EE',49385:'\u71E6',49386:'\u71E5',49387:'\u71ED',49388:'\u71EC',49389:'\u71F4',49390:'\u71E0',49391:'\u7235',49392:'\u7246',49393:'\u7370',49394:'\u7372',49395:'\u74A9',49396:'\u74B0',49397:'\u74A6',49398:'\u74A8',49399:'\u7646',49400:'\u7642',49401:'\u764C',49402:'\u76EA',49403:'\u77B3',49404:'\u77AA',49405:'\u77B0',49406:'\u77AC',49472:'\u77A7',49473:'\u77AD',49474:'\u77EF',49475:'\u78F7',49476:'\u78FA',49477:'\u78F4',49478:'\u78EF',49479:'\u7901',49480:'\u79A7',49481:'\u79AA',49482:'\u7A57',49483:'\u7ABF',49484:'\u7C07',49485:'\u7C0D',49486:'\u7BFE',49487:'\u7BF7',49488:'\u7C0C',49489:'\u7BE0',49490:'\u7CE0',49491:'\u7CDC',49492:'\u7CDE',49493:'\u7CE2',49494:'\u7CDF',49495:'\u7CD9',49496:'\u7CDD',49497:'\u7E2E',49498:'\u7E3E',49499:'\u7E46',49500:'\u7E37',49501:'\u7E32',49502:'\u7E43',49503:'\u7E2B',49504:'\u7E3D',49505:'\u7E31',49506:'\u7E45',49507:'\u7E41',49508:'\u7E34',49509:'\u7E39',49510:'\u7E48',49511:'\u7E35',49512:'\u7E3F',49513:'\u7E2F',49514:'\u7F44',49515:'\u7FF3',49516:'\u7FFC',49517:'\u8071',49518:'\u8072',49519:'\u8070',49520:'\u806F',49521:'\u8073',49522:'\u81C6',49523:'\u81C3',49524:'\u81BA',49525:'\u81C2',49526:'\u81C0',49527:'\u81BF',49528:'\u81BD',49529:'\u81C9',49530:'\u81BE',49531:'\u81E8',49532:'\u8209',49533:'\u8271',49534:'\u85AA',49569:'\u8584',49570:'\u857E',49571:'\u859C',49572:'\u8591',49573:'\u8594',49574:'\u85AF',49575:'\u859B',49576:'\u8587',49577:'\u85A8',49578:'\u858A',49579:'\u8667',49580:'\u87C0',49581:'\u87D1',49582:'\u87B3',49583:'\u87D2',49584:'\u87C6',49585:'\u87AB',49586:'\u87BB',49587:'\u87BA',49588:'\u87C8',49589:'\u87CB',49590:'\u893B',49591:'\u8936',49592:'\u8944',49593:'\u8938',49594:'\u893D',49595:'\u89AC',49596:'\u8B0E',49597:'\u8B17',49598:'\u8B19',49599:'\u8B1B',49600:'\u8B0A',49601:'\u8B20',49602:'\u8B1D',49603:'\u8B04',49604:'\u8B10',49605:'\u8C41',49606:'\u8C3F',49607:'\u8C73',49608:'\u8CFA',49609:'\u8CFD',49610:'\u8CFC',49611:'\u8CF8',49612:'\u8CFB',49613:'\u8DA8',49614:'\u8E49',49615:'\u8E4B',49616:'\u8E48',49617:'\u8E4A',49618:'\u8F44',49619:'\u8F3E',49620:'\u8F42',49621:'\u8F45',49622:'\u8F3F',49623:'\u907F',49624:'\u907D',49625:'\u9084',49626:'\u9081',49627:'\u9082',49628:'\u9080',49629:'\u9139',49630:'\u91A3',49631:'\u919E',49632:'\u919C',49633:'\u934D',49634:'\u9382',49635:'\u9328',49636:'\u9375',49637:'\u934A',49638:'\u9365',49639:'\u934B',49640:'\u9318',49641:'\u937E',49642:'\u936C',49643:'\u935B',49644:'\u9370',49645:'\u935A',49646:'\u9354',49647:'\u95CA',49648:'\u95CB',49649:'\u95CC',49650:'\u95C8',49651:'\u95C6',49652:'\u96B1',49653:'\u96B8',49654:'\u96D6',49655:'\u971C',49656:'\u971E',49657:'\u97A0',49658:'\u97D3',49659:'\u9846',49660:'\u98B6',49661:'\u9935',49662:'\u9A01',49728:'\u99FF',49729:'\u9BAE',49730:'\u9BAB',49731:'\u9BAA',49732:'\u9BAD',49733:'\u9D3B',49734:'\u9D3F',49735:'\u9E8B',49736:'\u9ECF',49737:'\u9EDE',49738:'\u9EDC',49739:'\u9EDD',49740:'\u9EDB',49741:'\u9F3E',49742:'\u9F4B',49743:'\u53E2',49744:'\u5695',49745:'\u56AE',49746:'\u58D9',49747:'\u58D8',49748:'\u5B38',49749:'\u5F5D',49750:'\u61E3',49751:'\u6233',49752:'\u64F4',49753:'\u64F2',49754:'\u64FE',49755:'\u6506',49756:'\u64FA',49757:'\u64FB',49758:'\u64F7',49759:'\u65B7',49760:'\u66DC',49761:'\u6726',49762:'\u6AB3',49763:'\u6AAC',49764:'\u6AC3',49765:'\u6ABB',49766:'\u6AB8',49767:'\u6AC2',49768:'\u6AAE',49769:'\u6AAF',49770:'\u6B5F',49771:'\u6B78',49772:'\u6BAF',49773:'\u7009',49774:'\u700B',49775:'\u6FFE',49776:'\u7006',49777:'\u6FFA',49778:'\u7011',49779:'\u700F',49780:'\u71FB',49781:'\u71FC',49782:'\u71FE',49783:'\u71F8',49784:'\u7377',49785:'\u7375',49786:'\u74A7',49787:'\u74BF',49788:'\u7515',49789:'\u7656',49790:'\u7658',49825:'\u7652',49826:'\u77BD',49827:'\u77BF',49828:'\u77BB',49829:'\u77BC',49830:'\u790E',49831:'\u79AE',49832:'\u7A61',49833:'\u7A62',49834:'\u7A60',49835:'\u7AC4',49836:'\u7AC5',49837:'\u7C2B',49838:'\u7C27',49839:'\u7C2A',49840:'\u7C1E',49841:'\u7C23',49842:'\u7C21',49843:'\u7CE7',49844:'\u7E54',49845:'\u7E55',49846:'\u7E5E',49847:'\u7E5A',49848:'\u7E61',49849:'\u7E52',49850:'\u7E59',49851:'\u7F48',49852:'\u7FF9',49853:'\u7FFB',49854:'\u8077',49855:'\u8076',49856:'\u81CD',49857:'\u81CF',49858:'\u820A',49859:'\u85CF',49860:'\u85A9',49861:'\u85CD',49862:'\u85D0',49863:'\u85C9',49864:'\u85B0',49865:'\u85BA',49866:'\u85B9',49867:'\u85A6',49868:'\u87EF',49869:'\u87EC',49870:'\u87F2',49871:'\u87E0',49872:'\u8986',49873:'\u89B2',49874:'\u89F4',49875:'\u8B28',49876:'\u8B39',49877:'\u8B2C',49878:'\u8B2B',49879:'\u8C50',49880:'\u8D05',49881:'\u8E59',49882:'\u8E63',49883:'\u8E66',49884:'\u8E64',49885:'\u8E5F',49886:'\u8E55',49887:'\u8EC0',49888:'\u8F49',49889:'\u8F4D',49890:'\u9087',49891:'\u9083',49892:'\u9088',49893:'\u91AB',49894:'\u91AC',49895:'\u91D0',49896:'\u9394',49897:'\u938A',49898:'\u9396',49899:'\u93A2',49900:'\u93B3',49901:'\u93AE',49902:'\u93AC',49903:'\u93B0',49904:'\u9398',49905:'\u939A',49906:'\u9397',49907:'\u95D4',49908:'\u95D6',49909:'\u95D0',49910:'\u95D5',49911:'\u96E2',49912:'\u96DC',49913:'\u96D9',49914:'\u96DB',49915:'\u96DE',49916:'\u9724',49917:'\u97A3',49918:'\u97A6',49984:'\u97AD',49985:'\u97F9',49986:'\u984D',49987:'\u984F',49988:'\u984C',49989:'\u984E',49990:'\u9853',49991:'\u98BA',49992:'\u993E',49993:'\u993F',49994:'\u993D',49995:'\u992E',49996:'\u99A5',49997:'\u9A0E',49998:'\u9AC1',49999:'\u9B03',50000:'\u9B06',50001:'\u9B4F',50002:'\u9B4E',50003:'\u9B4D',50004:'\u9BCA',50005:'\u9BC9',50006:'\u9BFD',50007:'\u9BC8',50008:'\u9BC0',50009:'\u9D51',50010:'\u9D5D',50011:'\u9D60',50012:'\u9EE0',50013:'\u9F15',50014:'\u9F2C',50015:'\u5133',50016:'\u56A5',50017:'\u58DE',50018:'\u58DF',50019:'\u58E2',50020:'\u5BF5',50021:'\u9F90',50022:'\u5EEC',50023:'\u61F2',50024:'\u61F7',50025:'\u61F6',50026:'\u61F5',50027:'\u6500',50028:'\u650F',50029:'\u66E0',50030:'\u66DD',50031:'\u6AE5',50032:'\u6ADD',50033:'\u6ADA',50034:'\u6AD3',50035:'\u701B',50036:'\u701F',50037:'\u7028',50038:'\u701A',50039:'\u701D',50040:'\u7015',50041:'\u7018',50042:'\u7206',50043:'\u720D',50044:'\u7258',50045:'\u72A2',50046:'\u7378',50081:'\u737A',50082:'\u74BD',50083:'\u74CA',50084:'\u74E3',50085:'\u7587',50086:'\u7586',50087:'\u765F',50088:'\u7661',50089:'\u77C7',50090:'\u7919',50091:'\u79B1',50092:'\u7A6B',50093:'\u7A69',50094:'\u7C3E',50095:'\u7C3F',50096:'\u7C38',50097:'\u7C3D',50098:'\u7C37',50099:'\u7C40',50100:'\u7E6B',50101:'\u7E6D',50102:'\u7E79',50103:'\u7E69',50104:'\u7E6A',50105:'\u7F85',50106:'\u7E73',50107:'\u7FB6',50108:'\u7FB9',50109:'\u7FB8',50110:'\u81D8',50111:'\u85E9',50112:'\u85DD',50113:'\u85EA',50114:'\u85D5',50115:'\u85E4',50116:'\u85E5',50117:'\u85F7',50118:'\u87FB',50119:'\u8805',50120:'\u880D',50121:'\u87F9',50122:'\u87FE',50123:'\u8960',50124:'\u895F',50125:'\u8956',50126:'\u895E',50127:'\u8B41',50128:'\u8B5C',50129:'\u8B58',50130:'\u8B49',50131:'\u8B5A',50132:'\u8B4E',50133:'\u8B4F',50134:'\u8B46',50135:'\u8B59',50136:'\u8D08',50137:'\u8D0A',50138:'\u8E7C',50139:'\u8E72',50140:'\u8E87',50141:'\u8E76',50142:'\u8E6C',50143:'\u8E7A',50144:'\u8E74',50145:'\u8F54',50146:'\u8F4E',50147:'\u8FAD',50148:'\u908A',50149:'\u908B',50150:'\u91B1',50151:'\u91AE',50152:'\u93E1',50153:'\u93D1',50154:'\u93DF',50155:'\u93C3',50156:'\u93C8',50157:'\u93DC',50158:'\u93DD',50159:'\u93D6',50160:'\u93E2',50161:'\u93CD',50162:'\u93D8',50163:'\u93E4',50164:'\u93D7',50165:'\u93E8',50166:'\u95DC',50167:'\u96B4',50168:'\u96E3',50169:'\u972A',50170:'\u9727',50171:'\u9761',50172:'\u97DC',50173:'\u97FB',50174:'\u985E',50240:'\u9858',50241:'\u985B',50242:'\u98BC',50243:'\u9945',50244:'\u9949',50245:'\u9A16',50246:'\u9A19',50247:'\u9B0D',50248:'\u9BE8',50249:'\u9BE7',50250:'\u9BD6',50251:'\u9BDB',50252:'\u9D89',50253:'\u9D61',50254:'\u9D72',50255:'\u9D6A',50256:'\u9D6C',50257:'\u9E92',50258:'\u9E97',50259:'\u9E93',50260:'\u9EB4',50261:'\u52F8',50262:'\u56A8',50263:'\u56B7',50264:'\u56B6',50265:'\u56B4',50266:'\u56BC',50267:'\u58E4',50268:'\u5B40',50269:'\u5B43',50270:'\u5B7D',50271:'\u5BF6',50272:'\u5DC9',50273:'\u61F8',50274:'\u61FA',50275:'\u6518',50276:'\u6514',50277:'\u6519',50278:'\u66E6',50279:'\u6727',50280:'\u6AEC',50281:'\u703E',50282:'\u7030',50283:'\u7032',50284:'\u7210',50285:'\u737B',50286:'\u74CF',50287:'\u7662',50288:'\u7665',50289:'\u7926',50290:'\u792A',50291:'\u792C',50292:'\u792B',50293:'\u7AC7',50294:'\u7AF6',50295:'\u7C4C',50296:'\u7C43',50297:'\u7C4D',50298:'\u7CEF',50299:'\u7CF0',50300:'\u8FAE',50301:'\u7E7D',50302:'\u7E7C',50337:'\u7E82',50338:'\u7F4C',50339:'\u8000',50340:'\u81DA',50341:'\u8266',50342:'\u85FB',50343:'\u85F9',50344:'\u8611',50345:'\u85FA',50346:'\u8606',50347:'\u860B',50348:'\u8607',50349:'\u860A',50350:'\u8814',50351:'\u8815',50352:'\u8964',50353:'\u89BA',50354:'\u89F8',50355:'\u8B70',50356:'\u8B6C',50357:'\u8B66',50358:'\u8B6F',50359:'\u8B5F',50360:'\u8B6B',50361:'\u8D0F',50362:'\u8D0D',50363:'\u8E89',50364:'\u8E81',50365:'\u8E85',50366:'\u8E82',50367:'\u91B4',50368:'\u91CB',50369:'\u9418',50370:'\u9403',50371:'\u93FD',50372:'\u95E1',50373:'\u9730',50374:'\u98C4',50375:'\u9952',50376:'\u9951',50377:'\u99A8',50378:'\u9A2B',50379:'\u9A30',50380:'\u9A37',50381:'\u9A35',50382:'\u9C13',50383:'\u9C0D',50384:'\u9E79',50385:'\u9EB5',50386:'\u9EE8',50387:'\u9F2F',50388:'\u9F5F',50389:'\u9F63',50390:'\u9F61',50391:'\u5137',50392:'\u5138',50393:'\u56C1',50394:'\u56C0',50395:'\u56C2',50396:'\u5914',50397:'\u5C6C',50398:'\u5DCD',50399:'\u61FC',50400:'\u61FE',50401:'\u651D',50402:'\u651C',50403:'\u6595',50404:'\u66E9',50405:'\u6AFB',50406:'\u6B04',50407:'\u6AFA',50408:'\u6BB2',50409:'\u704C',50410:'\u721B',50411:'\u72A7',50412:'\u74D6',50413:'\u74D4',50414:'\u7669',50415:'\u77D3',50416:'\u7C50',50417:'\u7E8F',50418:'\u7E8C',50419:'\u7FBC',50420:'\u8617',50421:'\u862D',50422:'\u861A',50423:'\u8823',50424:'\u8822',50425:'\u8821',50426:'\u881F',50427:'\u896A',50428:'\u896C',50429:'\u89BD',50430:'\u8B74',50496:'\u8B77',50497:'\u8B7D',50498:'\u8D13',50499:'\u8E8A',50500:'\u8E8D',50501:'\u8E8B',50502:'\u8F5F',50503:'\u8FAF',50504:'\u91BA',50505:'\u942E',50506:'\u9433',50507:'\u9435',50508:'\u943A',50509:'\u9438',50510:'\u9432',50511:'\u942B',50512:'\u95E2',50513:'\u9738',50514:'\u9739',50515:'\u9732',50516:'\u97FF',50517:'\u9867',50518:'\u9865',50519:'\u9957',50520:'\u9A45',50521:'\u9A43',50522:'\u9A40',50523:'\u9A3E',50524:'\u9ACF',50525:'\u9B54',50526:'\u9B51',50527:'\u9C2D',50528:'\u9C25',50529:'\u9DAF',50530:'\u9DB4',50531:'\u9DC2',50532:'\u9DB8',50533:'\u9E9D',50534:'\u9EEF',50535:'\u9F19',50536:'\u9F5C',50537:'\u9F66',50538:'\u9F67',50539:'\u513C',50540:'\u513B',50541:'\u56C8',50542:'\u56CA',50543:'\u56C9',50544:'\u5B7F',50545:'\u5DD4',50546:'\u5DD2',50547:'\u5F4E',50548:'\u61FF',50549:'\u6524',50550:'\u6B0A',50551:'\u6B61',50552:'\u7051',50553:'\u7058',50554:'\u7380',50555:'\u74E4',50556:'\u758A',50557:'\u766E',50558:'\u766C',50593:'\u79B3',50594:'\u7C60',50595:'\u7C5F',50596:'\u807E',50597:'\u807D',50598:'\u81DF',50599:'\u8972',50600:'\u896F',50601:'\u89FC',50602:'\u8B80',50603:'\u8D16',50604:'\u8D17',50605:'\u8E91',50606:'\u8E93',50607:'\u8F61',50608:'\u9148',50609:'\u9444',50610:'\u9451',50611:'\u9452',50612:'\u973D',50613:'\u973E',50614:'\u97C3',50615:'\u97C1',50616:'\u986B',50617:'\u9955',50618:'\u9A55',50619:'\u9A4D',50620:'\u9AD2',50621:'\u9B1A',50622:'\u9C49',50623:'\u9C31',50624:'\u9C3E',50625:'\u9C3B',50626:'\u9DD3',50627:'\u9DD7',50628:'\u9F34',50629:'\u9F6C',50630:'\u9F6A',50631:'\u9F94',50632:'\u56CC',50633:'\u5DD6',50634:'\u6200',50635:'\u6523',50636:'\u652B',50637:'\u652A',50638:'\u66EC',50639:'\u6B10',50640:'\u74DA',50641:'\u7ACA',50642:'\u7C64',50643:'\u7C63',50644:'\u7C65',50645:'\u7E93',50646:'\u7E96',50647:'\u7E94',50648:'\u81E2',50649:'\u8638',50650:'\u863F',50651:'\u8831',50652:'\u8B8A',50653:'\u9090',50654:'\u908F',50655:'\u9463',50656:'\u9460',50657:'\u9464',50658:'\u9768',50659:'\u986F',50660:'\u995C',50661:'\u9A5A',50662:'\u9A5B',50663:'\u9A57',50664:'\u9AD3',50665:'\u9AD4',50666:'\u9AD1',50667:'\u9C54',50668:'\u9C57',50669:'\u9C56',50670:'\u9DE5',50671:'\u9E9F',50672:'\u9EF4',50673:'\u56D1',50674:'\u58E9',50675:'\u652C',50676:'\u705E',50677:'\u7671',50678:'\u7672',50679:'\u77D7',50680:'\u7F50',50681:'\u7F88',50682:'\u8836',50683:'\u8839',50684:'\u8862',50685:'\u8B93',50686:'\u8B92',50752:'\u8B96',50753:'\u8277',50754:'\u8D1B',50755:'\u91C0',50756:'\u946A',50757:'\u9742',50758:'\u9748',50759:'\u9744',50760:'\u97C6',50761:'\u9870',50762:'\u9A5F',50763:'\u9B22',50764:'\u9B58',50765:'\u9C5F',50766:'\u9DF9',50767:'\u9DFA',50768:'\u9E7C',50769:'\u9E7D',50770:'\u9F07',50771:'\u9F77',50772:'\u9F72',50773:'\u5EF3',50774:'\u6B16',50775:'\u7063',50776:'\u7C6C',50777:'\u7C6E',50778:'\u883B',50779:'\u89C0',50780:'\u8EA1',50781:'\u91C1',50782:'\u9472',50783:'\u9470',50784:'\u9871',50785:'\u995E',50786:'\u9AD6',50787:'\u9B23',50788:'\u9ECC',50789:'\u7064',50790:'\u77DA',50791:'\u8B9A',50792:'\u9477',50793:'\u97C9',50794:'\u9A62',50795:'\u9A65',50796:'\u7E9C',50797:'\u8B9C',50798:'\u8EAA',50799:'\u91C5',50800:'\u947D',50801:'\u947E',50802:'\u947C',50803:'\u9C77',50804:'\u9C78',50805:'\u9EF7',50806:'\u8C54',50807:'\u947F',50808:'\u9E1A',50809:'\u7228',50810:'\u9A6A',50811:'\u9B31',50812:'\u9E1B',50813:'\u9E1E',50814:'\u7C72',50849:'\uF6B1',50850:'\uF6B2',50851:'\uF6B3',50852:'\uF6B4',50853:'\uF6B5',50854:'\uF6B6',50855:'\uF6B7',50856:'\uF6B8',50857:'\uF6B9',50858:'\uF6BA',50859:'\uF6BB',50860:'\uF6BC',50861:'\uF6BD',50862:'\uF6BE',50863:'\uF6BF',50864:'\uF6C0',50865:'\uF6C1',50866:'\uF6C2',50867:'\uF6C3',50868:'\uF6C4',50869:'\uF6C5',50870:'\uF6C6',50871:'\uF6C7',50872:'\uF6C8',50873:'\uF6C9',50874:'\uF6CA',50875:'\uF6CB',50876:'\uF6CC',50877:'\uF6CD',50878:'\uF6CE',50879:'\uF6CF',50880:'\uF6D0',50881:'\uF6D1',50882:'\uF6D2',50883:'\uF6D3',50884:'\uF6D4',50885:'\uF6D5',50886:'\uF6D6',50887:'\uF6D7',50888:'\uF6D8',50889:'\uF6D9',50890:'\uF6DA',50891:'\uF6DB',50892:'\uF6DC',50893:'\uF6DD',50894:'\uF6DE',50895:'\uF6DF',50896:'\uF6E0',50897:'\uF6E1',50898:'\uF6E2',50899:'\uF6E3',50900:'\uF6E4',50901:'\uF6E5',50902:'\uF6E6',50903:'\uF6E7',50904:'\uF6E8',50905:'\uF6E9',50906:'\uF6EA',50907:'\uF6EB',50908:'\uF6EC',50909:'\uF6ED',50910:'\uF6EE',50911:'\uF6EF',50912:'\uF6F0',50913:'\uF6F1',50914:'\uF6F2',50915:'\uF6F3',50916:'\uF6F4',50917:'\uF6F5',50918:'\uF6F6',50919:'\uF6F7',50920:'\uF6F8',50921:'\uF6F9',50922:'\uF6FA',50923:'\uF6FB',50924:'\uF6FC',50925:'\uF6FD',50926:'\uF6FE',50927:'\uF6FF',50928:'\uF700',50929:'\uF701',50930:'\uF702',50931:'\uF703',50932:'\uF704',50933:'\uF705',50934:'\uF706',50935:'\uF707',50936:'\uF708',50937:'\uF709',50938:'\uF70A',50939:'\uF70B',50940:'\uF70C',50941:'\uF70D',50942:'\uF70E',51008:'\uF70F',51009:'\uF710',51010:'\uF711',51011:'\uF712',51012:'\uF713',51013:'\uF714',51014:'\uF715',51015:'\uF716',51016:'\uF717',51017:'\uF718',51018:'\uF719',51019:'\uF71A',51020:'\uF71B',51021:'\uF71C',51022:'\uF71D',51023:'\uF71E',51024:'\uF71F',51025:'\uF720',51026:'\uF721',51027:'\uF722',51028:'\uF723',51029:'\uF724',51030:'\uF725',51031:'\uF726',51032:'\uF727',51033:'\uF728',51034:'\uF729',51035:'\uF72A',51036:'\uF72B',51037:'\uF72C',51038:'\uF72D',51039:'\uF72E',51040:'\uF72F',51041:'\uF730',51042:'\uF731',51043:'\uF732',51044:'\uF733',51045:'\uF734',51046:'\uF735',51047:'\uF736',51048:'\uF737',51049:'\uF738',51050:'\uF739',51051:'\uF73A',51052:'\uF73B',51053:'\uF73C',51054:'\uF73D',51055:'\uF73E',51056:'\uF73F',51057:'\uF740',51058:'\uF741',51059:'\uF742',51060:'\uF743',51061:'\uF744',51062:'\uF745',51063:'\uF746',51064:'\uF747',51065:'\uF748',51066:'\uF749',51067:'\uF74A',51068:'\uF74B',51069:'\uF74C',51070:'\uF74D',51105:'\uF74E',51106:'\uF74F',51107:'\uF750',51108:'\uF751',51109:'\uF752',51110:'\uF753',51111:'\uF754',51112:'\uF755',51113:'\uF756',51114:'\uF757',51115:'\uF758',51116:'\uF759',51117:'\uF75A',51118:'\uF75B',51119:'\uF75C',51120:'\uF75D',51121:'\uF75E',51122:'\uF75F',51123:'\uF760',51124:'\uF761',51125:'\uF762',51126:'\uF763',51127:'\uF764',51128:'\uF765',51129:'\uF766',51130:'\uF767',51131:'\uF768',51132:'\uF769',51133:'\uF76A',51134:'\uF76B',51135:'\uF76C',51136:'\uF76D',51137:'\uF76E',51138:'\uF76F',51139:'\uF770',51140:'\uF771',51141:'\uF772',51142:'\uF773',51143:'\uF774',51144:'\uF775',51145:'\uF776',51146:'\uF777',51147:'\uF778',51148:'\uF779',51149:'\uF77A',51150:'\uF77B',51151:'\uF77C',51152:'\uF77D',51153:'\uF77E',51154:'\uF77F',51155:'\uF780',51156:'\uF781',51157:'\uF782',51158:'\uF783',51159:'\uF784',51160:'\uF785',51161:'\uF786',51162:'\uF787',51163:'\uF788',51164:'\uF789',51165:'\uF78A',51166:'\uF78B',51167:'\uF78C',51168:'\uF78D',51169:'\uF78E',51170:'\uF78F',51171:'\uF790',51172:'\uF791',51173:'\uF792',51174:'\uF793',51175:'\uF794',51176:'\uF795',51177:'\uF796',51178:'\uF797',51179:'\uF798',51180:'\uF799',51181:'\uF79A',51182:'\uF79B',51183:'\uF79C',51184:'\uF79D',51185:'\uF79E',51186:'\uF79F',51187:'\uF7A0',51188:'\uF7A1',51189:'\uF7A2',51190:'\uF7A3',51191:'\uF7A4',51192:'\uF7A5',51193:'\uF7A6',51194:'\uF7A7',51195:'\uF7A8',51196:'\uF7A9',51197:'\uF7AA',51198:'\uF7AB',51264:'\uF7AC',51265:'\uF7AD',51266:'\uF7AE',51267:'\uF7AF',51268:'\uF7B0',51269:'\uF7B1',51270:'\uF7B2',51271:'\uF7B3',51272:'\uF7B4',51273:'\uF7B5',51274:'\uF7B6',51275:'\uF7B7',51276:'\uF7B8',51277:'\uF7B9',51278:'\uF7BA',51279:'\uF7BB',51280:'\uF7BC',51281:'\uF7BD',51282:'\uF7BE',51283:'\uF7BF',51284:'\uF7C0',51285:'\uF7C1',51286:'\uF7C2',51287:'\uF7C3',51288:'\uF7C4',51289:'\uF7C5',51290:'\uF7C6',51291:'\uF7C7',51292:'\uF7C8',51293:'\uF7C9',51294:'\uF7CA',51295:'\uF7CB',51296:'\uF7CC',51297:'\uF7CD',51298:'\uF7CE',51299:'\uF7CF',51300:'\uF7D0',51301:'\uF7D1',51302:'\uF7D2',51303:'\uF7D3',51304:'\uF7D4',51305:'\uF7D5',51306:'\uF7D6',51307:'\uF7D7',51308:'\uF7D8',51309:'\uF7D9',51310:'\uF7DA',51311:'\uF7DB',51312:'\uF7DC',51313:'\uF7DD',51314:'\uF7DE',51315:'\uF7DF',51316:'\uF7E0',51317:'\uF7E1',51318:'\uF7E2',51319:'\uF7E3',51320:'\uF7E4',51321:'\uF7E5',51322:'\uF7E6',51323:'\uF7E7',51324:'\uF7E8',51325:'\uF7E9',51326:'\uF7EA',51361:'\uF7EB',51362:'\uF7EC',51363:'\uF7ED',51364:'\uF7EE',51365:'\uF7EF',51366:'\uF7F0',51367:'\uF7F1',51368:'\uF7F2',51369:'\uF7F3',51370:'\uF7F4',51371:'\uF7F5',51372:'\uF7F6',51373:'\uF7F7',51374:'\uF7F8',51375:'\uF7F9',51376:'\uF7FA',51377:'\uF7FB',51378:'\uF7FC',51379:'\uF7FD',51380:'\uF7FE',51381:'\uF7FF',51382:'\uF800',51383:'\uF801',51384:'\uF802',51385:'\uF803',51386:'\uF804',51387:'\uF805',51388:'\uF806',51389:'\uF807',51390:'\uF808',51391:'\uF809',51392:'\uF80A',51393:'\uF80B',51394:'\uF80C',51395:'\uF80D',51396:'\uF80E',51397:'\uF80F',51398:'\uF810',51399:'\uF811',51400:'\uF812',51401:'\uF813',51402:'\uF814',51403:'\uF815',51404:'\uF816',51405:'\uF817',51406:'\uF818',51407:'\uF819',51408:'\uF81A',51409:'\uF81B',51410:'\uF81C',51411:'\uF81D',51412:'\uF81E',51413:'\uF81F',51414:'\uF820',51415:'\uF821',51416:'\uF822',51417:'\uF823',51418:'\uF824',51419:'\uF825',51420:'\uF826',51421:'\uF827',51422:'\uF828',51423:'\uF829',51424:'\uF82A',51425:'\uF82B',51426:'\uF82C',51427:'\uF82D',51428:'\uF82E',51429:'\uF82F',51430:'\uF830',51431:'\uF831',51432:'\uF832',51433:'\uF833',51434:'\uF834',51435:'\uF835',51436:'\uF836',51437:'\uF837',51438:'\uF838',51439:'\uF839',51440:'\uF83A',51441:'\uF83B',51442:'\uF83C',51443:'\uF83D',51444:'\uF83E',51445:'\uF83F',51446:'\uF840',51447:'\uF841',51448:'\uF842',51449:'\uF843',51450:'\uF844',51451:'\uF845',51452:'\uF846',51453:'\uF847',51454:'\uF848',51520:'\u4E42',51521:'\u4E5C',51522:'\u51F5',51523:'\u531A',51524:'\u5382',51525:'\u4E07',51526:'\u4E0C',51527:'\u4E47',51528:'\u4E8D',51529:'\u56D7',51530:'\uFA0C',51531:'\u5C6E',51532:'\u5F73',51533:'\u4E0F',51534:'\u5187',51535:'\u4E0E',51536:'\u4E2E',51537:'\u4E93',51538:'\u4EC2',51539:'\u4EC9',51540:'\u4EC8',51541:'\u5198',51542:'\u52FC',51543:'\u536C',51544:'\u53B9',51545:'\u5720',51546:'\u5903',51547:'\u592C',51548:'\u5C10',51549:'\u5DFF',51550:'\u65E1',51551:'\u6BB3',51552:'\u6BCC',51553:'\u6C14',51554:'\u723F',51555:'\u4E31',51556:'\u4E3C',51557:'\u4EE8',51558:'\u4EDC',51559:'\u4EE9',51560:'\u4EE1',51561:'\u4EDD',51562:'\u4EDA',51563:'\u520C',51564:'\u531C',51565:'\u534C',51566:'\u5722',51567:'\u5723',51568:'\u5917',51569:'\u592F',51570:'\u5B81',51571:'\u5B84',51572:'\u5C12',51573:'\u5C3B',51574:'\u5C74',51575:'\u5C73',51576:'\u5E04',51577:'\u5E80',51578:'\u5E82',51579:'\u5FC9',51580:'\u6209',51581:'\u6250',51582:'\u6C15',51617:'\u6C36',51618:'\u6C43',51619:'\u6C3F',51620:'\u6C3B',51621:'\u72AE',51622:'\u72B0',51623:'\u738A',51624:'\u79B8',51625:'\u808A',51626:'\u961E',51627:'\u4F0E',51628:'\u4F18',51629:'\u4F2C',51630:'\u4EF5',51631:'\u4F14',51632:'\u4EF1',51633:'\u4F00',51634:'\u4EF7',51635:'\u4F08',51636:'\u4F1D',51637:'\u4F02',51638:'\u4F05',51639:'\u4F22',51640:'\u4F13',51641:'\u4F04',51642:'\u4EF4',51643:'\u4F12',51644:'\u51B1',51645:'\u5213',51646:'\u5209',51647:'\u5210',51648:'\u52A6',51649:'\u5322',51650:'\u531F',51651:'\u534D',51652:'\u538A',51653:'\u5407',51654:'\u56E1',51655:'\u56DF',51656:'\u572E',51657:'\u572A',51658:'\u5734',51659:'\u593C',51660:'\u5980',51661:'\u597C',51662:'\u5985',51663:'\u597B',51664:'\u597E',51665:'\u5977',51666:'\u597F',51667:'\u5B56',51668:'\u5C15',51669:'\u5C25',51670:'\u5C7C',51671:'\u5C7A',51672:'\u5C7B',51673:'\u5C7E',51674:'\u5DDF',51675:'\u5E75',51676:'\u5E84',51677:'\u5F02',51678:'\u5F1A',51679:'\u5F74',51680:'\u5FD5',51681:'\u5FD4',51682:'\u5FCF',51683:'\u625C',51684:'\u625E',51685:'\u6264',51686:'\u6261',51687:'\u6266',51688:'\u6262',51689:'\u6259',51690:'\u6260',51691:'\u625A',51692:'\u6265',51693:'\u65EF',51694:'\u65EE',51695:'\u673E',51696:'\u6739',51697:'\u6738',51698:'\u673B',51699:'\u673A',51700:'\u673F',51701:'\u673C',51702:'\u6733',51703:'\u6C18',51704:'\u6C46',51705:'\u6C52',51706:'\u6C5C',51707:'\u6C4F',51708:'\u6C4A',51709:'\u6C54',51710:'\u6C4B',51776:'\u6C4C',51777:'\u7071',51778:'\u725E',51779:'\u72B4',51780:'\u72B5',51781:'\u738E',51782:'\u752A',51783:'\u767F',51784:'\u7A75',51785:'\u7F51',51786:'\u8278',51787:'\u827C',51788:'\u8280',51789:'\u827D',51790:'\u827F',51791:'\u864D',51792:'\u897E',51793:'\u9099',51794:'\u9097',51795:'\u9098',51796:'\u909B',51797:'\u9094',51798:'\u9622',51799:'\u9624',51800:'\u9620',51801:'\u9623',51802:'\u4F56',51803:'\u4F3B',51804:'\u4F62',51805:'\u4F49',51806:'\u4F53',51807:'\u4F64',51808:'\u4F3E',51809:'\u4F67',51810:'\u4F52',51811:'\u4F5F',51812:'\u4F41',51813:'\u4F58',51814:'\u4F2D',51815:'\u4F33',51816:'\u4F3F',51817:'\u4F61',51818:'\u518F',51819:'\u51B9',51820:'\u521C',51821:'\u521E',51822:'\u5221',51823:'\u52AD',51824:'\u52AE',51825:'\u5309',51826:'\u5363',51827:'\u5372',51828:'\u538E',51829:'\u538F',51830:'\u5430',51831:'\u5437',51832:'\u542A',51833:'\u5454',51834:'\u5445',51835:'\u5419',51836:'\u541C',51837:'\u5425',51838:'\u5418',51873:'\u543D',51874:'\u544F',51875:'\u5441',51876:'\u5428',51877:'\u5424',51878:'\u5447',51879:'\u56EE',51880:'\u56E7',51881:'\u56E5',51882:'\u5741',51883:'\u5745',51884:'\u574C',51885:'\u5749',51886:'\u574B',51887:'\u5752',51888:'\u5906',51889:'\u5940',51890:'\u59A6',51891:'\u5998',51892:'\u59A0',51893:'\u5997',51894:'\u598E',51895:'\u59A2',51896:'\u5990',51897:'\u598F',51898:'\u59A7',51899:'\u59A1',51900:'\u5B8E',51901:'\u5B92',51902:'\u5C28',51903:'\u5C2A',51904:'\u5C8D',51905:'\u5C8F',51906:'\u5C88',51907:'\u5C8B',51908:'\u5C89',51909:'\u5C92',51910:'\u5C8A',51911:'\u5C86',51912:'\u5C93',51913:'\u5C95',51914:'\u5DE0',51915:'\u5E0A',51916:'\u5E0E',51917:'\u5E8B',51918:'\u5E89',51919:'\u5E8C',51920:'\u5E88',51921:'\u5E8D',51922:'\u5F05',51923:'\u5F1D',51924:'\u5F78',51925:'\u5F76',51926:'\u5FD2',51927:'\u5FD1',51928:'\u5FD0',51929:'\u5FED',51930:'\u5FE8',51931:'\u5FEE',51932:'\u5FF3',51933:'\u5FE1',51934:'\u5FE4',51935:'\u5FE3',51936:'\u5FFA',51937:'\u5FEF',51938:'\u5FF7',51939:'\u5FFB',51940:'\u6000',51941:'\u5FF4',51942:'\u623A',51943:'\u6283',51944:'\u628C',51945:'\u628E',51946:'\u628F',51947:'\u6294',51948:'\u6287',51949:'\u6271',51950:'\u627B',51951:'\u627A',51952:'\u6270',51953:'\u6281',51954:'\u6288',51955:'\u6277',51956:'\u627D',51957:'\u6272',51958:'\u6274',51959:'\u6537',51960:'\u65F0',51961:'\u65F4',51962:'\u65F3',51963:'\u65F2',51964:'\u65F5',51965:'\u6745',51966:'\u6747',52032:'\u6759',52033:'\u6755',52034:'\u674C',52035:'\u6748',52036:'\u675D',52037:'\u674D',52038:'\u675A',52039:'\u674B',52040:'\u6BD0',52041:'\u6C19',52042:'\u6C1A',52043:'\u6C78',52044:'\u6C67',52045:'\u6C6B',52046:'\u6C84',52047:'\u6C8B',52048:'\u6C8F',52049:'\u6C71',52050:'\u6C6F',52051:'\u6C69',52052:'\u6C9A',52053:'\u6C6D',52054:'\u6C87',52055:'\u6C95',52056:'\u6C9C',52057:'\u6C66',52058:'\u6C73',52059:'\u6C65',52060:'\u6C7B',52061:'\u6C8E',52062:'\u7074',52063:'\u707A',52064:'\u7263',52065:'\u72BF',52066:'\u72BD',52067:'\u72C3',52068:'\u72C6',52069:'\u72C1',52070:'\u72BA',52071:'\u72C5',52072:'\u7395',52073:'\u7397',52074:'\u7393',52075:'\u7394',52076:'\u7392',52077:'\u753A',52078:'\u7539',52079:'\u7594',52080:'\u7595',52081:'\u7681',52082:'\u793D',52083:'\u8034',52084:'\u8095',52085:'\u8099',52086:'\u8090',52087:'\u8092',52088:'\u809C',52089:'\u8290',52090:'\u828F',52091:'\u8285',52092:'\u828E',52093:'\u8291',52094:'\u8293',52129:'\u828A',52130:'\u8283',52131:'\u8284',52132:'\u8C78',52133:'\u8FC9',52134:'\u8FBF',52135:'\u909F',52136:'\u90A1',52137:'\u90A5',52138:'\u909E',52139:'\u90A7',52140:'\u90A0',52141:'\u9630',52142:'\u9628',52143:'\u962F',52144:'\u962D',52145:'\u4E33',52146:'\u4F98',52147:'\u4F7C',52148:'\u4F85',52149:'\u4F7D',52150:'\u4F80',52151:'\u4F87',52152:'\u4F76',52153:'\u4F74',52154:'\u4F89',52155:'\u4F84',52156:'\u4F77',52157:'\u4F4C',52158:'\u4F97',52159:'\u4F6A',52160:'\u4F9A',52161:'\u4F79',52162:'\u4F81',52163:'\u4F78',52164:'\u4F90',52165:'\u4F9C',52166:'\u4F94',52167:'\u4F9E',52168:'\u4F92',52169:'\u4F82',52170:'\u4F95',52171:'\u4F6B',52172:'\u4F6E',52173:'\u519E',52174:'\u51BC',52175:'\u51BE',52176:'\u5235',52177:'\u5232',52178:'\u5233',52179:'\u5246',52180:'\u5231',52181:'\u52BC',52182:'\u530A',52183:'\u530B',52184:'\u533C',52185:'\u5392',52186:'\u5394',52187:'\u5487',52188:'\u547F',52189:'\u5481',52190:'\u5491',52191:'\u5482',52192:'\u5488',52193:'\u546B',52194:'\u547A',52195:'\u547E',52196:'\u5465',52197:'\u546C',52198:'\u5474',52199:'\u5466',52200:'\u548D',52201:'\u546F',52202:'\u5461',52203:'\u5460',52204:'\u5498',52205:'\u5463',52206:'\u5467',52207:'\u5464',52208:'\u56F7',52209:'\u56F9',52210:'\u576F',52211:'\u5772',52212:'\u576D',52213:'\u576B',52214:'\u5771',52215:'\u5770',52216:'\u5776',52217:'\u5780',52218:'\u5775',52219:'\u577B',52220:'\u5773',52221:'\u5774',52222:'\u5762',52288:'\u5768',52289:'\u577D',52290:'\u590C',52291:'\u5945',52292:'\u59B5',52293:'\u59BA',52294:'\u59CF',52295:'\u59CE',52296:'\u59B2',52297:'\u59CC',52298:'\u59C1',52299:'\u59B6',52300:'\u59BC',52301:'\u59C3',52302:'\u59D6',52303:'\u59B1',52304:'\u59BD',52305:'\u59C0',52306:'\u59C8',52307:'\u59B4',52308:'\u59C7',52309:'\u5B62',52310:'\u5B65',52311:'\u5B93',52312:'\u5B95',52313:'\u5C44',52314:'\u5C47',52315:'\u5CAE',52316:'\u5CA4',52317:'\u5CA0',52318:'\u5CB5',52319:'\u5CAF',52320:'\u5CA8',52321:'\u5CAC',52322:'\u5C9F',52323:'\u5CA3',52324:'\u5CAD',52325:'\u5CA2',52326:'\u5CAA',52327:'\u5CA7',52328:'\u5C9D',52329:'\u5CA5',52330:'\u5CB6',52331:'\u5CB0',52332:'\u5CA6',52333:'\u5E17',52334:'\u5E14',52335:'\u5E19',52336:'\u5F28',52337:'\u5F22',52338:'\u5F23',52339:'\u5F24',52340:'\u5F54',52341:'\u5F82',52342:'\u5F7E',52343:'\u5F7D',52344:'\u5FDE',52345:'\u5FE5',52346:'\u602D',52347:'\u6026',52348:'\u6019',52349:'\u6032',52350:'\u600B',52385:'\u6034',52386:'\u600A',52387:'\u6017',52388:'\u6033',52389:'\u601A',52390:'\u601E',52391:'\u602C',52392:'\u6022',52393:'\u600D',52394:'\u6010',52395:'\u602E',52396:'\u6013',52397:'\u6011',52398:'\u600C',52399:'\u6009',52400:'\u601C',52401:'\u6214',52402:'\u623D',52403:'\u62AD',52404:'\u62B4',52405:'\u62D1',52406:'\u62BE',52407:'\u62AA',52408:'\u62B6',52409:'\u62CA',52410:'\u62AE',52411:'\u62B3',52412:'\u62AF',52413:'\u62BB',52414:'\u62A9',52415:'\u62B0',52416:'\u62B8',52417:'\u653D',52418:'\u65A8',52419:'\u65BB',52420:'\u6609',52421:'\u65FC',52422:'\u6604',52423:'\u6612',52424:'\u6608',52425:'\u65FB',52426:'\u6603',52427:'\u660B',52428:'\u660D',52429:'\u6605',52430:'\u65FD',52431:'\u6611',52432:'\u6610',52433:'\u66F6',52434:'\u670A',52435:'\u6785',52436:'\u676C',52437:'\u678E',52438:'\u6792',52439:'\u6776',52440:'\u677B',52441:'\u6798',52442:'\u6786',52443:'\u6784',52444:'\u6774',52445:'\u678D',52446:'\u678C',52447:'\u677A',52448:'\u679F',52449:'\u6791',52450:'\u6799',52451:'\u6783',52452:'\u677D',52453:'\u6781',52454:'\u6778',52455:'\u6779',52456:'\u6794',52457:'\u6B25',52458:'\u6B80',52459:'\u6B7E',52460:'\u6BDE',52461:'\u6C1D',52462:'\u6C93',52463:'\u6CEC',52464:'\u6CEB',52465:'\u6CEE',52466:'\u6CD9',52467:'\u6CB6',52468:'\u6CD4',52469:'\u6CAD',52470:'\u6CE7',52471:'\u6CB7',52472:'\u6CD0',52473:'\u6CC2',52474:'\u6CBA',52475:'\u6CC3',52476:'\u6CC6',52477:'\u6CED',52478:'\u6CF2',52544:'\u6CD2',52545:'\u6CDD',52546:'\u6CB4',52547:'\u6C8A',52548:'\u6C9D',52549:'\u6C80',52550:'\u6CDE',52551:'\u6CC0',52552:'\u6D30',52553:'\u6CCD',52554:'\u6CC7',52555:'\u6CB0',52556:'\u6CF9',52557:'\u6CCF',52558:'\u6CE9',52559:'\u6CD1',52560:'\u7094',52561:'\u7098',52562:'\u7085',52563:'\u7093',52564:'\u7086',52565:'\u7084',52566:'\u7091',52567:'\u7096',52568:'\u7082',52569:'\u709A',52570:'\u7083',52571:'\u726A',52572:'\u72D6',52573:'\u72CB',52574:'\u72D8',52575:'\u72C9',52576:'\u72DC',52577:'\u72D2',52578:'\u72D4',52579:'\u72DA',52580:'\u72CC',52581:'\u72D1',52582:'\u73A4',52583:'\u73A1',52584:'\u73AD',52585:'\u73A6',52586:'\u73A2',52587:'\u73A0',52588:'\u73AC',52589:'\u739D',52590:'\u74DD',52591:'\u74E8',52592:'\u753F',52593:'\u7540',52594:'\u753E',52595:'\u758C',52596:'\u7598',52597:'\u76AF',52598:'\u76F3',52599:'\u76F1',52600:'\u76F0',52601:'\u76F5',52602:'\u77F8',52603:'\u77FC',52604:'\u77F9',52605:'\u77FB',52606:'\u77FA',52641:'\u77F7',52642:'\u7942',52643:'\u793F',52644:'\u79C5',52645:'\u7A78',52646:'\u7A7B',52647:'\u7AFB',52648:'\u7C75',52649:'\u7CFD',52650:'\u8035',52651:'\u808F',52652:'\u80AE',52653:'\u80A3',52654:'\u80B8',52655:'\u80B5',52656:'\u80AD',52657:'\u8220',52658:'\u82A0',52659:'\u82C0',52660:'\u82AB',52661:'\u829A',52662:'\u8298',52663:'\u829B',52664:'\u82B5',52665:'\u82A7',52666:'\u82AE',52667:'\u82BC',52668:'\u829E',52669:'\u82BA',52670:'\u82B4',52671:'\u82A8',52672:'\u82A1',52673:'\u82A9',52674:'\u82C2',52675:'\u82A4',52676:'\u82C3',52677:'\u82B6',52678:'\u82A2',52679:'\u8670',52680:'\u866F',52681:'\u866D',52682:'\u866E',52683:'\u8C56',52684:'\u8FD2',52685:'\u8FCB',52686:'\u8FD3',52687:'\u8FCD',52688:'\u8FD6',52689:'\u8FD5',52690:'\u8FD7',52691:'\u90B2',52692:'\u90B4',52693:'\u90AF',52694:'\u90B3',52695:'\u90B0',52696:'\u9639',52697:'\u963D',52698:'\u963C',52699:'\u963A',52700:'\u9643',52701:'\u4FCD',52702:'\u4FC5',52703:'\u4FD3',52704:'\u4FB2',52705:'\u4FC9',52706:'\u4FCB',52707:'\u4FC1',52708:'\u4FD4',52709:'\u4FDC',52710:'\u4FD9',52711:'\u4FBB',52712:'\u4FB3',52713:'\u4FDB',52714:'\u4FC7',52715:'\u4FD6',52716:'\u4FBA',52717:'\u4FC0',52718:'\u4FB9',52719:'\u4FEC',52720:'\u5244',52721:'\u5249',52722:'\u52C0',52723:'\u52C2',52724:'\u533D',52725:'\u537C',52726:'\u5397',52727:'\u5396',52728:'\u5399',52729:'\u5398',52730:'\u54BA',52731:'\u54A1',52732:'\u54AD',52733:'\u54A5',52734:'\u54CF',52800:'\u54C3',52801:'\u830D',52802:'\u54B7',52803:'\u54AE',52804:'\u54D6',52805:'\u54B6',52806:'\u54C5',52807:'\u54C6',52808:'\u54A0',52809:'\u5470',52810:'\u54BC',52811:'\u54A2',52812:'\u54BE',52813:'\u5472',52814:'\u54DE',52815:'\u54B0',52816:'\u57B5',52817:'\u579E',52818:'\u579F',52819:'\u57A4',52820:'\u578C',52821:'\u5797',52822:'\u579D',52823:'\u579B',52824:'\u5794',52825:'\u5798',52826:'\u578F',52827:'\u5799',52828:'\u57A5',52829:'\u579A',52830:'\u5795',52831:'\u58F4',52832:'\u590D',52833:'\u5953',52834:'\u59E1',52835:'\u59DE',52836:'\u59EE',52837:'\u5A00',52838:'\u59F1',52839:'\u59DD',52840:'\u59FA',52841:'\u59FD',52842:'\u59FC',52843:'\u59F6',52844:'\u59E4',52845:'\u59F2',52846:'\u59F7',52847:'\u59DB',52848:'\u59E9',52849:'\u59F3',52850:'\u59F5',52851:'\u59E0',52852:'\u59FE',52853:'\u59F4',52854:'\u59ED',52855:'\u5BA8',52856:'\u5C4C',52857:'\u5CD0',52858:'\u5CD8',52859:'\u5CCC',52860:'\u5CD7',52861:'\u5CCB',52862:'\u5CDB',52897:'\u5CDE',52898:'\u5CDA',52899:'\u5CC9',52900:'\u5CC7',52901:'\u5CCA',52902:'\u5CD6',52903:'\u5CD3',52904:'\u5CD4',52905:'\u5CCF',52906:'\u5CC8',52907:'\u5CC6',52908:'\u5CCE',52909:'\u5CDF',52910:'\u5CF8',52911:'\u5DF9',52912:'\u5E21',52913:'\u5E22',52914:'\u5E23',52915:'\u5E20',52916:'\u5E24',52917:'\u5EB0',52918:'\u5EA4',52919:'\u5EA2',52920:'\u5E9B',52921:'\u5EA3',52922:'\u5EA5',52923:'\u5F07',52924:'\u5F2E',52925:'\u5F56',52926:'\u5F86',52927:'\u6037',52928:'\u6039',52929:'\u6054',52930:'\u6072',52931:'\u605E',52932:'\u6045',52933:'\u6053',52934:'\u6047',52935:'\u6049',52936:'\u605B',52937:'\u604C',52938:'\u6040',52939:'\u6042',52940:'\u605F',52941:'\u6024',52942:'\u6044',52943:'\u6058',52944:'\u6066',52945:'\u606E',52946:'\u6242',52947:'\u6243',52948:'\u62CF',52949:'\u630D',52950:'\u630B',52951:'\u62F5',52952:'\u630E',52953:'\u6303',52954:'\u62EB',52955:'\u62F9',52956:'\u630F',52957:'\u630C',52958:'\u62F8',52959:'\u62F6',52960:'\u6300',52961:'\u6313',52962:'\u6314',52963:'\u62FA',52964:'\u6315',52965:'\u62FB',52966:'\u62F0',52967:'\u6541',52968:'\u6543',52969:'\u65AA',52970:'\u65BF',52971:'\u6636',52972:'\u6621',52973:'\u6632',52974:'\u6635',52975:'\u661C',52976:'\u6626',52977:'\u6622',52978:'\u6633',52979:'\u662B',52980:'\u663A',52981:'\u661D',52982:'\u6634',52983:'\u6639',52984:'\u662E',52985:'\u670F',52986:'\u6710',52987:'\u67C1',52988:'\u67F2',52989:'\u67C8',52990:'\u67BA',53056:'\u67DC',53057:'\u67BB',53058:'\u67F8',53059:'\u67D8',53060:'\u67C0',53061:'\u67B7',53062:'\u67C5',53063:'\u67EB',53064:'\u67E4',53065:'\u67DF',53066:'\u67B5',53067:'\u67CD',53068:'\u67B3',53069:'\u67F7',53070:'\u67F6',53071:'\u67EE',53072:'\u67E3',53073:'\u67C2',53074:'\u67B9',53075:'\u67CE',53076:'\u67E7',53077:'\u67F0',53078:'\u67B2',53079:'\u67FC',53080:'\u67C6',53081:'\u67ED',53082:'\u67CC',53083:'\u67AE',53084:'\u67E6',53085:'\u67DB',53086:'\u67FA',53087:'\u67C9',53088:'\u67CA',53089:'\u67C3',53090:'\u67EA',53091:'\u67CB',53092:'\u6B28',53093:'\u6B82',53094:'\u6B84',53095:'\u6BB6',53096:'\u6BD6',53097:'\u6BD8',53098:'\u6BE0',53099:'\u6C20',53100:'\u6C21',53101:'\u6D28',53102:'\u6D34',53103:'\u6D2D',53104:'\u6D1F',53105:'\u6D3C',53106:'\u6D3F',53107:'\u6D12',53108:'\u6D0A',53109:'\u6CDA',53110:'\u6D33',53111:'\u6D04',53112:'\u6D19',53113:'\u6D3A',53114:'\u6D1A',53115:'\u6D11',53116:'\u6D00',53117:'\u6D1D',53118:'\u6D42',53153:'\u6D01',53154:'\u6D18',53155:'\u6D37',53156:'\u6D03',53157:'\u6D0F',53158:'\u6D40',53159:'\u6D07',53160:'\u6D20',53161:'\u6D2C',53162:'\u6D08',53163:'\u6D22',53164:'\u6D09',53165:'\u6D10',53166:'\u70B7',53167:'\u709F',53168:'\u70BE',53169:'\u70B1',53170:'\u70B0',53171:'\u70A1',53172:'\u70B4',53173:'\u70B5',53174:'\u70A9',53175:'\u7241',53176:'\u7249',53177:'\u724A',53178:'\u726C',53179:'\u7270',53180:'\u7273',53181:'\u726E',53182:'\u72CA',53183:'\u72E4',53184:'\u72E8',53185:'\u72EB',53186:'\u72DF',53187:'\u72EA',53188:'\u72E6',53189:'\u72E3',53190:'\u7385',53191:'\u73CC',53192:'\u73C2',53193:'\u73C8',53194:'\u73C5',53195:'\u73B9',53196:'\u73B6',53197:'\u73B5',53198:'\u73B4',53199:'\u73EB',53200:'\u73BF',53201:'\u73C7',53202:'\u73BE',53203:'\u73C3',53204:'\u73C6',53205:'\u73B8',53206:'\u73CB',53207:'\u74EC',53208:'\u74EE',53209:'\u752E',53210:'\u7547',53211:'\u7548',53212:'\u75A7',53213:'\u75AA',53214:'\u7679',53215:'\u76C4',53216:'\u7708',53217:'\u7703',53218:'\u7704',53219:'\u7705',53220:'\u770A',53221:'\u76F7',53222:'\u76FB',53223:'\u76FA',53224:'\u77E7',53225:'\u77E8',53226:'\u7806',53227:'\u7811',53228:'\u7812',53229:'\u7805',53230:'\u7810',53231:'\u780F',53232:'\u780E',53233:'\u7809',53234:'\u7803',53235:'\u7813',53236:'\u794A',53237:'\u794C',53238:'\u794B',53239:'\u7945',53240:'\u7944',53241:'\u79D5',53242:'\u79CD',53243:'\u79CF',53244:'\u79D6',53245:'\u79CE',53246:'\u7A80',53312:'\u7A7E',53313:'\u7AD1',53314:'\u7B00',53315:'\u7B01',53316:'\u7C7A',53317:'\u7C78',53318:'\u7C79',53319:'\u7C7F',53320:'\u7C80',53321:'\u7C81',53322:'\u7D03',53323:'\u7D08',53324:'\u7D01',53325:'\u7F58',53326:'\u7F91',53327:'\u7F8D',53328:'\u7FBE',53329:'\u8007',53330:'\u800E',53331:'\u800F',53332:'\u8014',53333:'\u8037',53334:'\u80D8',53335:'\u80C7',53336:'\u80E0',53337:'\u80D1',53338:'\u80C8',53339:'\u80C2',53340:'\u80D0',53341:'\u80C5',53342:'\u80E3',53343:'\u80D9',53344:'\u80DC',53345:'\u80CA',53346:'\u80D5',53347:'\u80C9',53348:'\u80CF',53349:'\u80D7',53350:'\u80E6',53351:'\u80CD',53352:'\u81FF',53353:'\u8221',53354:'\u8294',53355:'\u82D9',53356:'\u82FE',53357:'\u82F9',53358:'\u8307',53359:'\u82E8',53360:'\u8300',53361:'\u82D5',53362:'\u833A',53363:'\u82EB',53364:'\u82D6',53365:'\u82F4',53366:'\u82EC',53367:'\u82E1',53368:'\u82F2',53369:'\u82F5',53370:'\u830C',53371:'\u82FB',53372:'\u82F6',53373:'\u82F0',53374:'\u82EA',53409:'\u82E4',53410:'\u82E0',53411:'\u82FA',53412:'\u82F3',53413:'\u82ED',53414:'\u8677',53415:'\u8674',53416:'\u867C',53417:'\u8673',53418:'\u8841',53419:'\u884E',53420:'\u8867',53421:'\u886A',53422:'\u8869',53423:'\u89D3',53424:'\u8A04',53425:'\u8A07',53426:'\u8D72',53427:'\u8FE3',53428:'\u8FE1',53429:'\u8FEE',53430:'\u8FE0',53431:'\u90F1',53432:'\u90BD',53433:'\u90BF',53434:'\u90D5',53435:'\u90C5',53436:'\u90BE',53437:'\u90C7',53438:'\u90CB',53439:'\u90C8',53440:'\u91D4',53441:'\u91D3',53442:'\u9654',53443:'\u964F',53444:'\u9651',53445:'\u9653',53446:'\u964A',53447:'\u964E',53448:'\u501E',53449:'\u5005',53450:'\u5007',53451:'\u5013',53452:'\u5022',53453:'\u5030',53454:'\u501B',53455:'\u4FF5',53456:'\u4FF4',53457:'\u5033',53458:'\u5037',53459:'\u502C',53460:'\u4FF6',53461:'\u4FF7',53462:'\u5017',53463:'\u501C',53464:'\u5020',53465:'\u5027',53466:'\u5035',53467:'\u502F',53468:'\u5031',53469:'\u500E',53470:'\u515A',53471:'\u5194',53472:'\u5193',53473:'\u51CA',53474:'\u51C4',53475:'\u51C5',53476:'\u51C8',53477:'\u51CE',53478:'\u5261',53479:'\u525A',53480:'\u5252',53481:'\u525E',53482:'\u525F',53483:'\u5255',53484:'\u5262',53485:'\u52CD',53486:'\u530E',53487:'\u539E',53488:'\u5526',53489:'\u54E2',53490:'\u5517',53491:'\u5512',53492:'\u54E7',53493:'\u54F3',53494:'\u54E4',53495:'\u551A',53496:'\u54FF',53497:'\u5504',53498:'\u5508',53499:'\u54EB',53500:'\u5511',53501:'\u5505',53502:'\u54F1',53568:'\u550A',53569:'\u54FB',53570:'\u54F7',53571:'\u54F8',53572:'\u54E0',53573:'\u550E',53574:'\u5503',53575:'\u550B',53576:'\u5701',53577:'\u5702',53578:'\u57CC',53579:'\u5832',53580:'\u57D5',53581:'\u57D2',53582:'\u57BA',53583:'\u57C6',53584:'\u57BD',53585:'\u57BC',53586:'\u57B8',53587:'\u57B6',53588:'\u57BF',53589:'\u57C7',53590:'\u57D0',53591:'\u57B9',53592:'\u57C1',53593:'\u590E',53594:'\u594A',53595:'\u5A19',53596:'\u5A16',53597:'\u5A2D',53598:'\u5A2E',53599:'\u5A15',53600:'\u5A0F',53601:'\u5A17',53602:'\u5A0A',53603:'\u5A1E',53604:'\u5A33',53605:'\u5B6C',53606:'\u5BA7',53607:'\u5BAD',53608:'\u5BAC',53609:'\u5C03',53610:'\u5C56',53611:'\u5C54',53612:'\u5CEC',53613:'\u5CFF',53614:'\u5CEE',53615:'\u5CF1',53616:'\u5CF7',53617:'\u5D00',53618:'\u5CF9',53619:'\u5E29',53620:'\u5E28',53621:'\u5EA8',53622:'\u5EAE',53623:'\u5EAA',53624:'\u5EAC',53625:'\u5F33',53626:'\u5F30',53627:'\u5F67',53628:'\u605D',53629:'\u605A',53630:'\u6067',53665:'\u6041',53666:'\u60A2',53667:'\u6088',53668:'\u6080',53669:'\u6092',53670:'\u6081',53671:'\u609D',53672:'\u6083',53673:'\u6095',53674:'\u609B',53675:'\u6097',53676:'\u6087',53677:'\u609C',53678:'\u608E',53679:'\u6219',53680:'\u6246',53681:'\u62F2',53682:'\u6310',53683:'\u6356',53684:'\u632C',53685:'\u6344',53686:'\u6345',53687:'\u6336',53688:'\u6343',53689:'\u63E4',53690:'\u6339',53691:'\u634B',53692:'\u634A',53693:'\u633C',53694:'\u6329',53695:'\u6341',53696:'\u6334',53697:'\u6358',53698:'\u6354',53699:'\u6359',53700:'\u632D',53701:'\u6347',53702:'\u6333',53703:'\u635A',53704:'\u6351',53705:'\u6338',53706:'\u6357',53707:'\u6340',53708:'\u6348',53709:'\u654A',53710:'\u6546',53711:'\u65C6',53712:'\u65C3',53713:'\u65C4',53714:'\u65C2',53715:'\u664A',53716:'\u665F',53717:'\u6647',53718:'\u6651',53719:'\u6712',53720:'\u6713',53721:'\u681F',53722:'\u681A',53723:'\u6849',53724:'\u6832',53725:'\u6833',53726:'\u683B',53727:'\u684B',53728:'\u684F',53729:'\u6816',53730:'\u6831',53731:'\u681C',53732:'\u6835',53733:'\u682B',53734:'\u682D',53735:'\u682F',53736:'\u684E',53737:'\u6844',53738:'\u6834',53739:'\u681D',53740:'\u6812',53741:'\u6814',53742:'\u6826',53743:'\u6828',53744:'\u682E',53745:'\u684D',53746:'\u683A',53747:'\u6825',53748:'\u6820',53749:'\u6B2C',53750:'\u6B2F',53751:'\u6B2D',53752:'\u6B31',53753:'\u6B34',53754:'\u6B6D',53755:'\u8082',53756:'\u6B88',53757:'\u6BE6',53758:'\u6BE4',53824:'\u6BE8',53825:'\u6BE3',53826:'\u6BE2',53827:'\u6BE7',53828:'\u6C25',53829:'\u6D7A',53830:'\u6D63',53831:'\u6D64',53832:'\u6D76',53833:'\u6D0D',53834:'\u6D61',53835:'\u6D92',53836:'\u6D58',53837:'\u6D62',53838:'\u6D6D',53839:'\u6D6F',53840:'\u6D91',53841:'\u6D8D',53842:'\u6DEF',53843:'\u6D7F',53844:'\u6D86',53845:'\u6D5E',53846:'\u6D67',53847:'\u6D60',53848:'\u6D97',53849:'\u6D70',53850:'\u6D7C',53851:'\u6D5F',53852:'\u6D82',53853:'\u6D98',53854:'\u6D2F',53855:'\u6D68',53856:'\u6D8B',53857:'\u6D7E',53858:'\u6D80',53859:'\u6D84',53860:'\u6D16',53861:'\u6D83',53862:'\u6D7B',53863:'\u6D7D',53864:'\u6D75',53865:'\u6D90',53866:'\u70DC',53867:'\u70D3',53868:'\u70D1',53869:'\u70DD',53870:'\u70CB',53871:'\u7F39',53872:'\u70E2',53873:'\u70D7',53874:'\u70D2',53875:'\u70DE',53876:'\u70E0',53877:'\u70D4',53878:'\u70CD',53879:'\u70C5',53880:'\u70C6',53881:'\u70C7',53882:'\u70DA',53883:'\u70CE',53884:'\u70E1',53885:'\u7242',53886:'\u7278',53921:'\u7277',53922:'\u7276',53923:'\u7300',53924:'\u72FA',53925:'\u72F4',53926:'\u72FE',53927:'\u72F6',53928:'\u72F3',53929:'\u72FB',53930:'\u7301',53931:'\u73D3',53932:'\u73D9',53933:'\u73E5',53934:'\u73D6',53935:'\u73BC',53936:'\u73E7',53937:'\u73E3',53938:'\u73E9',53939:'\u73DC',53940:'\u73D2',53941:'\u73DB',53942:'\u73D4',53943:'\u73DD',53944:'\u73DA',53945:'\u73D7',53946:'\u73D8',53947:'\u73E8',53948:'\u74DE',53949:'\u74DF',53950:'\u74F4',53951:'\u74F5',53952:'\u7521',53953:'\u755B',53954:'\u755F',53955:'\u75B0',53956:'\u75C1',53957:'\u75BB',53958:'\u75C4',53959:'\u75C0',53960:'\u75BF',53961:'\u75B6',53962:'\u75BA',53963:'\u768A',53964:'\u76C9',53965:'\u771D',53966:'\u771B',53967:'\u7710',53968:'\u7713',53969:'\u7712',53970:'\u7723',53971:'\u7711',53972:'\u7715',53973:'\u7719',53974:'\u771A',53975:'\u7722',53976:'\u7727',53977:'\u7823',53978:'\u782C',53979:'\u7822',53980:'\u7835',53981:'\u782F',53982:'\u7828',53983:'\u782E',53984:'\u782B',53985:'\u7821',53986:'\u7829',53987:'\u7833',53988:'\u782A',53989:'\u7831',53990:'\u7954',53991:'\u795B',53992:'\u794F',53993:'\u795C',53994:'\u7953',53995:'\u7952',53996:'\u7951',53997:'\u79EB',53998:'\u79EC',53999:'\u79E0',54000:'\u79EE',54001:'\u79ED',54002:'\u79EA',54003:'\u79DC',54004:'\u79DE',54005:'\u79DD',54006:'\u7A86',54007:'\u7A89',54008:'\u7A85',54009:'\u7A8B',54010:'\u7A8C',54011:'\u7A8A',54012:'\u7A87',54013:'\u7AD8',54014:'\u7B10',54080:'\u7B04',54081:'\u7B13',54082:'\u7B05',54083:'\u7B0F',54084:'\u7B08',54085:'\u7B0A',54086:'\u7B0E',54087:'\u7B09',54088:'\u7B12',54089:'\u7C84',54090:'\u7C91',54091:'\u7C8A',54092:'\u7C8C',54093:'\u7C88',54094:'\u7C8D',54095:'\u7C85',54096:'\u7D1E',54097:'\u7D1D',54098:'\u7D11',54099:'\u7D0E',54100:'\u7D18',54101:'\u7D16',54102:'\u7D13',54103:'\u7D1F',54104:'\u7D12',54105:'\u7D0F',54106:'\u7D0C',54107:'\u7F5C',54108:'\u7F61',54109:'\u7F5E',54110:'\u7F60',54111:'\u7F5D',54112:'\u7F5B',54113:'\u7F96',54114:'\u7F92',54115:'\u7FC3',54116:'\u7FC2',54117:'\u7FC0',54118:'\u8016',54119:'\u803E',54120:'\u8039',54121:'\u80FA',54122:'\u80F2',54123:'\u80F9',54124:'\u80F5',54125:'\u8101',54126:'\u80FB',54127:'\u8100',54128:'\u8201',54129:'\u822F',54130:'\u8225',54131:'\u8333',54132:'\u832D',54133:'\u8344',54134:'\u8319',54135:'\u8351',54136:'\u8325',54137:'\u8356',54138:'\u833F',54139:'\u8341',54140:'\u8326',54141:'\u831C',54142:'\u8322',54177:'\u8342',54178:'\u834E',54179:'\u831B',54180:'\u832A',54181:'\u8308',54182:'\u833C',54183:'\u834D',54184:'\u8316',54185:'\u8324',54186:'\u8320',54187:'\u8337',54188:'\u832F',54189:'\u8329',54190:'\u8347',54191:'\u8345',54192:'\u834C',54193:'\u8353',54194:'\u831E',54195:'\u832C',54196:'\u834B',54197:'\u8327',54198:'\u8348',54199:'\u8653',54200:'\u8652',54201:'\u86A2',54202:'\u86A8',54203:'\u8696',54204:'\u868D',54205:'\u8691',54206:'\u869E',54207:'\u8687',54208:'\u8697',54209:'\u8686',54210:'\u868B',54211:'\u869A',54212:'\u8685',54213:'\u86A5',54214:'\u8699',54215:'\u86A1',54216:'\u86A7',54217:'\u8695',54218:'\u8698',54219:'\u868E',54220:'\u869D',54221:'\u8690',54222:'\u8694',54223:'\u8843',54224:'\u8844',54225:'\u886D',54226:'\u8875',54227:'\u8876',54228:'\u8872',54229:'\u8880',54230:'\u8871',54231:'\u887F',54232:'\u886F',54233:'\u8883',54234:'\u887E',54235:'\u8874',54236:'\u887C',54237:'\u8A12',54238:'\u8C47',54239:'\u8C57',54240:'\u8C7B',54241:'\u8CA4',54242:'\u8CA3',54243:'\u8D76',54244:'\u8D78',54245:'\u8DB5',54246:'\u8DB7',54247:'\u8DB6',54248:'\u8ED1',54249:'\u8ED3',54250:'\u8FFE',54251:'\u8FF5',54252:'\u9002',54253:'\u8FFF',54254:'\u8FFB',54255:'\u9004',54256:'\u8FFC',54257:'\u8FF6',54258:'\u90D6',54259:'\u90E0',54260:'\u90D9',54261:'\u90DA',54262:'\u90E3',54263:'\u90DF',54264:'\u90E5',54265:'\u90D8',54266:'\u90DB',54267:'\u90D7',54268:'\u90DC',54269:'\u90E4',54270:'\u9150',54336:'\u914E',54337:'\u914F',54338:'\u91D5',54339:'\u91E2',54340:'\u91DA',54341:'\u965C',54342:'\u965F',54343:'\u96BC',54344:'\u98E3',54345:'\u9ADF',54346:'\u9B2F',54347:'\u4E7F',54348:'\u5070',54349:'\u506A',54350:'\u5061',54351:'\u505E',54352:'\u5060',54353:'\u5053',54354:'\u504B',54355:'\u505D',54356:'\u5072',54357:'\u5048',54358:'\u504D',54359:'\u5041',54360:'\u505B',54361:'\u504A',54362:'\u5062',54363:'\u5015',54364:'\u5045',54365:'\u505F',54366:'\u5069',54367:'\u506B',54368:'\u5063',54369:'\u5064',54370:'\u5046',54371:'\u5040',54372:'\u506E',54373:'\u5073',54374:'\u5057',54375:'\u5051',54376:'\u51D0',54377:'\u526B',54378:'\u526D',54379:'\u526C',54380:'\u526E',54381:'\u52D6',54382:'\u52D3',54383:'\u532D',54384:'\u539C',54385:'\u5575',54386:'\u5576',54387:'\u553C',54388:'\u554D',54389:'\u5550',54390:'\u5534',54391:'\u552A',54392:'\u5551',54393:'\u5562',54394:'\u5536',54395:'\u5535',54396:'\u5530',54397:'\u5552',54398:'\u5545',54433:'\u550C',54434:'\u5532',54435:'\u5565',54436:'\u554E',54437:'\u5539',54438:'\u5548',54439:'\u552D',54440:'\u553B',54441:'\u5540',54442:'\u554B',54443:'\u570A',54444:'\u5707',54445:'\u57FB',54446:'\u5814',54447:'\u57E2',54448:'\u57F6',54449:'\u57DC',54450:'\u57F4',54451:'\u5800',54452:'\u57ED',54453:'\u57FD',54454:'\u5808',54455:'\u57F8',54456:'\u580B',54457:'\u57F3',54458:'\u57CF',54459:'\u5807',54460:'\u57EE',54461:'\u57E3',54462:'\u57F2',54463:'\u57E5',54464:'\u57EC',54465:'\u57E1',54466:'\u580E',54467:'\u57FC',54468:'\u5810',54469:'\u57E7',54470:'\u5801',54471:'\u580C',54472:'\u57F1',54473:'\u57E9',54474:'\u57F0',54475:'\u580D',54476:'\u5804',54477:'\u595C',54478:'\u5A60',54479:'\u5A58',54480:'\u5A55',54481:'\u5A67',54482:'\u5A5E',54483:'\u5A38',54484:'\u5A35',54485:'\u5A6D',54486:'\u5A50',54487:'\u5A5F',54488:'\u5A65',54489:'\u5A6C',54490:'\u5A53',54491:'\u5A64',54492:'\u5A57',54493:'\u5A43',54494:'\u5A5D',54495:'\u5A52',54496:'\u5A44',54497:'\u5A5B',54498:'\u5A48',54499:'\u5A8E',54500:'\u5A3E',54501:'\u5A4D',54502:'\u5A39',54503:'\u5A4C',54504:'\u5A70',54505:'\u5A69',54506:'\u5A47',54507:'\u5A51',54508:'\u5A56',54509:'\u5A42',54510:'\u5A5C',54511:'\u5B72',54512:'\u5B6E',54513:'\u5BC1',54514:'\u5BC0',54515:'\u5C59',54516:'\u5D1E',54517:'\u5D0B',54518:'\u5D1D',54519:'\u5D1A',54520:'\u5D20',54521:'\u5D0C',54522:'\u5D28',54523:'\u5D0D',54524:'\u5D26',54525:'\u5D25',54526:'\u5D0F',54592:'\u5D30',54593:'\u5D12',54594:'\u5D23',54595:'\u5D1F',54596:'\u5D2E',54597:'\u5E3E',54598:'\u5E34',54599:'\u5EB1',54600:'\u5EB4',54601:'\u5EB9',54602:'\u5EB2',54603:'\u5EB3',54604:'\u5F36',54605:'\u5F38',54606:'\u5F9B',54607:'\u5F96',54608:'\u5F9F',54609:'\u608A',54610:'\u6090',54611:'\u6086',54612:'\u60BE',54613:'\u60B0',54614:'\u60BA',54615:'\u60D3',54616:'\u60D4',54617:'\u60CF',54618:'\u60E4',54619:'\u60D9',54620:'\u60DD',54621:'\u60C8',54622:'\u60B1',54623:'\u60DB',54624:'\u60B7',54625:'\u60CA',54626:'\u60BF',54627:'\u60C3',54628:'\u60CD',54629:'\u60C0',54630:'\u6332',54631:'\u6365',54632:'\u638A',54633:'\u6382',54634:'\u637D',54635:'\u63BD',54636:'\u639E',54637:'\u63AD',54638:'\u639D',54639:'\u6397',54640:'\u63AB',54641:'\u638E',54642:'\u636F',54643:'\u6387',54644:'\u6390',54645:'\u636E',54646:'\u63AF',54647:'\u6375',54648:'\u639C',54649:'\u636D',54650:'\u63AE',54651:'\u637C',54652:'\u63A4',54653:'\u633B',54654:'\u639F',54689:'\u6378',54690:'\u6385',54691:'\u6381',54692:'\u6391',54693:'\u638D',54694:'\u6370',54695:'\u6553',54696:'\u65CD',54697:'\u6665',54698:'\u6661',54699:'\u665B',54700:'\u6659',54701:'\u665C',54702:'\u6662',54703:'\u6718',54704:'\u6879',54705:'\u6887',54706:'\u6890',54707:'\u689C',54708:'\u686D',54709:'\u686E',54710:'\u68AE',54711:'\u68AB',54712:'\u6956',54713:'\u686F',54714:'\u68A3',54715:'\u68AC',54716:'\u68A9',54717:'\u6875',54718:'\u6874',54719:'\u68B2',54720:'\u688F',54721:'\u6877',54722:'\u6892',54723:'\u687C',54724:'\u686B',54725:'\u6872',54726:'\u68AA',54727:'\u6880',54728:'\u6871',54729:'\u687E',54730:'\u689B',54731:'\u6896',54732:'\u688B',54733:'\u68A0',54734:'\u6889',54735:'\u68A4',54736:'\u6878',54737:'\u687B',54738:'\u6891',54739:'\u688C',54740:'\u688A',54741:'\u687D',54742:'\u6B36',54743:'\u6B33',54744:'\u6B37',54745:'\u6B38',54746:'\u6B91',54747:'\u6B8F',54748:'\u6B8D',54749:'\u6B8E',54750:'\u6B8C',54751:'\u6C2A',54752:'\u6DC0',54753:'\u6DAB',54754:'\u6DB4',54755:'\u6DB3',54756:'\u6E74',54757:'\u6DAC',54758:'\u6DE9',54759:'\u6DE2',54760:'\u6DB7',54761:'\u6DF6',54762:'\u6DD4',54763:'\u6E00',54764:'\u6DC8',54765:'\u6DE0',54766:'\u6DDF',54767:'\u6DD6',54768:'\u6DBE',54769:'\u6DE5',54770:'\u6DDC',54771:'\u6DDD',54772:'\u6DDB',54773:'\u6DF4',54774:'\u6DCA',54775:'\u6DBD',54776:'\u6DED',54777:'\u6DF0',54778:'\u6DBA',54779:'\u6DD5',54780:'\u6DC2',54781:'\u6DCF',54782:'\u6DC9',54848:'\u6DD0',54849:'\u6DF2',54850:'\u6DD3',54851:'\u6DFD',54852:'\u6DD7',54853:'\u6DCD',54854:'\u6DE3',54855:'\u6DBB',54856:'\u70FA',54857:'\u710D',54858:'\u70F7',54859:'\u7117',54860:'\u70F4',54861:'\u710C',54862:'\u70F0',54863:'\u7104',54864:'\u70F3',54865:'\u7110',54866:'\u70FC',54867:'\u70FF',54868:'\u7106',54869:'\u7113',54870:'\u7100',54871:'\u70F8',54872:'\u70F6',54873:'\u710B',54874:'\u7102',54875:'\u710E',54876:'\u727E',54877:'\u727B',54878:'\u727C',54879:'\u727F',54880:'\u731D',54881:'\u7317',54882:'\u7307',54883:'\u7311',54884:'\u7318',54885:'\u730A',54886:'\u7308',54887:'\u72FF',54888:'\u730F',54889:'\u731E',54890:'\u7388',54891:'\u73F6',54892:'\u73F8',54893:'\u73F5',54894:'\u7404',54895:'\u7401',54896:'\u73FD',54897:'\u7407',54898:'\u7400',54899:'\u73FA',54900:'\u73FC',54901:'\u73FF',54902:'\u740C',54903:'\u740B',54904:'\u73F4',54905:'\u7408',54906:'\u7564',54907:'\u7563',54908:'\u75CE',54909:'\u75D2',54910:'\u75CF',54945:'\u75CB',54946:'\u75CC',54947:'\u75D1',54948:'\u75D0',54949:'\u768F',54950:'\u7689',54951:'\u76D3',54952:'\u7739',54953:'\u772F',54954:'\u772D',54955:'\u7731',54956:'\u7732',54957:'\u7734',54958:'\u7733',54959:'\u773D',54960:'\u7725',54961:'\u773B',54962:'\u7735',54963:'\u7848',54964:'\u7852',54965:'\u7849',54966:'\u784D',54967:'\u784A',54968:'\u784C',54969:'\u7826',54970:'\u7845',54971:'\u7850',54972:'\u7964',54973:'\u7967',54974:'\u7969',54975:'\u796A',54976:'\u7963',54977:'\u796B',54978:'\u7961',54979:'\u79BB',54980:'\u79FA',54981:'\u79F8',54982:'\u79F6',54983:'\u79F7',54984:'\u7A8F',54985:'\u7A94',54986:'\u7A90',54987:'\u7B35',54988:'\u7B47',54989:'\u7B34',54990:'\u7B25',54991:'\u7B30',54992:'\u7B22',54993:'\u7B24',54994:'\u7B33',54995:'\u7B18',54996:'\u7B2A',54997:'\u7B1D',54998:'\u7B31',54999:'\u7B2B',55000:'\u7B2D',55001:'\u7B2F',55002:'\u7B32',55003:'\u7B38',55004:'\u7B1A',55005:'\u7B23',55006:'\u7C94',55007:'\u7C98',55008:'\u7C96',55009:'\u7CA3',55010:'\u7D35',55011:'\u7D3D',55012:'\u7D38',55013:'\u7D36',55014:'\u7D3A',55015:'\u7D45',55016:'\u7D2C',55017:'\u7D29',55018:'\u7D41',55019:'\u7D47',55020:'\u7D3E',55021:'\u7D3F',55022:'\u7D4A',55023:'\u7D3B',55024:'\u7D28',55025:'\u7F63',55026:'\u7F95',55027:'\u7F9C',55028:'\u7F9D',55029:'\u7F9B',55030:'\u7FCA',55031:'\u7FCB',55032:'\u7FCD',55033:'\u7FD0',55034:'\u7FD1',55035:'\u7FC7',55036:'\u7FCF',55037:'\u7FC9',55038:'\u801F',55104:'\u801E',55105:'\u801B',55106:'\u8047',55107:'\u8043',55108:'\u8048',55109:'\u8118',55110:'\u8125',55111:'\u8119',55112:'\u811B',55113:'\u812D',55114:'\u811F',55115:'\u812C',55116:'\u811E',55117:'\u8121',55118:'\u8115',55119:'\u8127',55120:'\u811D',55121:'\u8122',55122:'\u8211',55123:'\u8238',55124:'\u8233',55125:'\u823A',55126:'\u8234',55127:'\u8232',55128:'\u8274',55129:'\u8390',55130:'\u83A3',55131:'\u83A8',55132:'\u838D',55133:'\u837A',55134:'\u8373',55135:'\u83A4',55136:'\u8374',55137:'\u838F',55138:'\u8381',55139:'\u8395',55140:'\u8399',55141:'\u8375',55142:'\u8394',55143:'\u83A9',55144:'\u837D',55145:'\u8383',55146:'\u838C',55147:'\u839D',55148:'\u839B',55149:'\u83AA',55150:'\u838B',55151:'\u837E',55152:'\u83A5',55153:'\u83AF',55154:'\u8388',55155:'\u8397',55156:'\u83B0',55157:'\u837F',55158:'\u83A6',55159:'\u8387',55160:'\u83AE',55161:'\u8376',55162:'\u839A',55163:'\u8659',55164:'\u8656',55165:'\u86BF',55166:'\u86B7',55201:'\u86C2',55202:'\u86C1',55203:'\u86C5',55204:'\u86BA',55205:'\u86B0',55206:'\u86C8',55207:'\u86B9',55208:'\u86B3',55209:'\u86B8',55210:'\u86CC',55211:'\u86B4',55212:'\u86BB',55213:'\u86BC',55214:'\u86C3',55215:'\u86BD',55216:'\u86BE',55217:'\u8852',55218:'\u8889',55219:'\u8895',55220:'\u88A8',55221:'\u88A2',55222:'\u88AA',55223:'\u889A',55224:'\u8891',55225:'\u88A1',55226:'\u889F',55227:'\u8898',55228:'\u88A7',55229:'\u8899',55230:'\u889B',55231:'\u8897',55232:'\u88A4',55233:'\u88AC',55234:'\u888C',55235:'\u8893',55236:'\u888E',55237:'\u8982',55238:'\u89D6',55239:'\u89D9',55240:'\u89D5',55241:'\u8A30',55242:'\u8A27',55243:'\u8A2C',55244:'\u8A1E',55245:'\u8C39',55246:'\u8C3B',55247:'\u8C5C',55248:'\u8C5D',55249:'\u8C7D',55250:'\u8CA5',55251:'\u8D7D',55252:'\u8D7B',55253:'\u8D79',55254:'\u8DBC',55255:'\u8DC2',55256:'\u8DB9',55257:'\u8DBF',55258:'\u8DC1',55259:'\u8ED8',55260:'\u8EDE',55261:'\u8EDD',55262:'\u8EDC',55263:'\u8ED7',55264:'\u8EE0',55265:'\u8EE1',55266:'\u9024',55267:'\u900B',55268:'\u9011',55269:'\u901C',55270:'\u900C',55271:'\u9021',55272:'\u90EF',55273:'\u90EA',55274:'\u90F0',55275:'\u90F4',55276:'\u90F2',55277:'\u90F3',55278:'\u90D4',55279:'\u90EB',55280:'\u90EC',55281:'\u90E9',55282:'\u9156',55283:'\u9158',55284:'\u915A',55285:'\u9153',55286:'\u9155',55287:'\u91EC',55288:'\u91F4',55289:'\u91F1',55290:'\u91F3',55291:'\u91F8',55292:'\u91E4',55293:'\u91F9',55294:'\u91EA',55360:'\u91EB',55361:'\u91F7',55362:'\u91E8',55363:'\u91EE',55364:'\u957A',55365:'\u9586',55366:'\u9588',55367:'\u967C',55368:'\u966D',55369:'\u966B',55370:'\u9671',55371:'\u966F',55372:'\u96BF',55373:'\u976A',55374:'\u9804',55375:'\u98E5',55376:'\u9997',55377:'\u509B',55378:'\u5095',55379:'\u5094',55380:'\u509E',55381:'\u508B',55382:'\u50A3',55383:'\u5083',55384:'\u508C',55385:'\u508E',55386:'\u509D',55387:'\u5068',55388:'\u509C',55389:'\u5092',55390:'\u5082',55391:'\u5087',55392:'\u515F',55393:'\u51D4',55394:'\u5312',55395:'\u5311',55396:'\u53A4',55397:'\u53A7',55398:'\u5591',55399:'\u55A8',55400:'\u55A5',55401:'\u55AD',55402:'\u5577',55403:'\u5645',55404:'\u55A2',55405:'\u5593',55406:'\u5588',55407:'\u558F',55408:'\u55B5',55409:'\u5581',55410:'\u55A3',55411:'\u5592',55412:'\u55A4',55413:'\u557D',55414:'\u558C',55415:'\u55A6',55416:'\u557F',55417:'\u5595',55418:'\u55A1',55419:'\u558E',55420:'\u570C',55421:'\u5829',55422:'\u5837',55457:'\u5819',55458:'\u581E',55459:'\u5827',55460:'\u5823',55461:'\u5828',55462:'\u57F5',55463:'\u5848',55464:'\u5825',55465:'\u581C',55466:'\u581B',55467:'\u5833',55468:'\u583F',55469:'\u5836',55470:'\u582E',55471:'\u5839',55472:'\u5838',55473:'\u582D',55474:'\u582C',55475:'\u583B',55476:'\u5961',55477:'\u5AAF',55478:'\u5A94',55479:'\u5A9F',55480:'\u5A7A',55481:'\u5AA2',55482:'\u5A9E',55483:'\u5A78',55484:'\u5AA6',55485:'\u5A7C',55486:'\u5AA5',55487:'\u5AAC',55488:'\u5A95',55489:'\u5AAE',55490:'\u5A37',55491:'\u5A84',55492:'\u5A8A',55493:'\u5A97',55494:'\u5A83',55495:'\u5A8B',55496:'\u5AA9',55497:'\u5A7B',55498:'\u5A7D',55499:'\u5A8C',55500:'\u5A9C',55501:'\u5A8F',55502:'\u5A93',55503:'\u5A9D',55504:'\u5BEA',55505:'\u5BCD',55506:'\u5BCB',55507:'\u5BD4',55508:'\u5BD1',55509:'\u5BCA',55510:'\u5BCE',55511:'\u5C0C',55512:'\u5C30',55513:'\u5D37',55514:'\u5D43',55515:'\u5D6B',55516:'\u5D41',55517:'\u5D4B',55518:'\u5D3F',55519:'\u5D35',55520:'\u5D51',55521:'\u5D4E',55522:'\u5D55',55523:'\u5D33',55524:'\u5D3A',55525:'\u5D52',55526:'\u5D3D',55527:'\u5D31',55528:'\u5D59',55529:'\u5D42',55530:'\u5D39',55531:'\u5D49',55532:'\u5D38',55533:'\u5D3C',55534:'\u5D32',55535:'\u5D36',55536:'\u5D40',55537:'\u5D45',55538:'\u5E44',55539:'\u5E41',55540:'\u5F58',55541:'\u5FA6',55542:'\u5FA5',55543:'\u5FAB',55544:'\u60C9',55545:'\u60B9',55546:'\u60CC',55547:'\u60E2',55548:'\u60CE',55549:'\u60C4',55550:'\u6114',55616:'\u60F2',55617:'\u610A',55618:'\u6116',55619:'\u6105',55620:'\u60F5',55621:'\u6113',55622:'\u60F8',55623:'\u60FC',55624:'\u60FE',55625:'\u60C1',55626:'\u6103',55627:'\u6118',55628:'\u611D',55629:'\u6110',55630:'\u60FF',55631:'\u6104',55632:'\u610B',55633:'\u624A',55634:'\u6394',55635:'\u63B1',55636:'\u63B0',55637:'\u63CE',55638:'\u63E5',55639:'\u63E8',55640:'\u63EF',55641:'\u63C3',55642:'\u649D',55643:'\u63F3',55644:'\u63CA',55645:'\u63E0',55646:'\u63F6',55647:'\u63D5',55648:'\u63F2',55649:'\u63F5',55650:'\u6461',55651:'\u63DF',55652:'\u63BE',55653:'\u63DD',55654:'\u63DC',55655:'\u63C4',55656:'\u63D8',55657:'\u63D3',55658:'\u63C2',55659:'\u63C7',55660:'\u63CC',55661:'\u63CB',55662:'\u63C8',55663:'\u63F0',55664:'\u63D7',55665:'\u63D9',55666:'\u6532',55667:'\u6567',55668:'\u656A',55669:'\u6564',55670:'\u655C',55671:'\u6568',55672:'\u6565',55673:'\u658C',55674:'\u659D',55675:'\u659E',55676:'\u65AE',55677:'\u65D0',55678:'\u65D2',55713:'\u667C',55714:'\u666C',55715:'\u667B',55716:'\u6680',55717:'\u6671',55718:'\u6679',55719:'\u666A',55720:'\u6672',55721:'\u6701',55722:'\u690C',55723:'\u68D3',55724:'\u6904',55725:'\u68DC',55726:'\u692A',55727:'\u68EC',55728:'\u68EA',55729:'\u68F1',55730:'\u690F',55731:'\u68D6',55732:'\u68F7',55733:'\u68EB',55734:'\u68E4',55735:'\u68F6',55736:'\u6913',55737:'\u6910',55738:'\u68F3',55739:'\u68E1',55740:'\u6907',55741:'\u68CC',55742:'\u6908',55743:'\u6970',55744:'\u68B4',55745:'\u6911',55746:'\u68EF',55747:'\u68C6',55748:'\u6914',55749:'\u68F8',55750:'\u68D0',55751:'\u68FD',55752:'\u68FC',55753:'\u68E8',55754:'\u690B',55755:'\u690A',55756:'\u6917',55757:'\u68CE',55758:'\u68C8',55759:'\u68DD',55760:'\u68DE',55761:'\u68E6',55762:'\u68F4',55763:'\u68D1',55764:'\u6906',55765:'\u68D4',55766:'\u68E9',55767:'\u6915',55768:'\u6925',55769:'\u68C7',55770:'\u6B39',55771:'\u6B3B',55772:'\u6B3F',55773:'\u6B3C',55774:'\u6B94',55775:'\u6B97',55776:'\u6B99',55777:'\u6B95',55778:'\u6BBD',55779:'\u6BF0',55780:'\u6BF2',55781:'\u6BF3',55782:'\u6C30',55783:'\u6DFC',55784:'\u6E46',55785:'\u6E47',55786:'\u6E1F',55787:'\u6E49',55788:'\u6E88',55789:'\u6E3C',55790:'\u6E3D',55791:'\u6E45',55792:'\u6E62',55793:'\u6E2B',55794:'\u6E3F',55795:'\u6E41',55796:'\u6E5D',55797:'\u6E73',55798:'\u6E1C',55799:'\u6E33',55800:'\u6E4B',55801:'\u6E40',55802:'\u6E51',55803:'\u6E3B',55804:'\u6E03',55805:'\u6E2E',55806:'\u6E5E',55872:'\u6E68',55873:'\u6E5C',55874:'\u6E61',55875:'\u6E31',55876:'\u6E28',55877:'\u6E60',55878:'\u6E71',55879:'\u6E6B',55880:'\u6E39',55881:'\u6E22',55882:'\u6E30',55883:'\u6E53',55884:'\u6E65',55885:'\u6E27',55886:'\u6E78',55887:'\u6E64',55888:'\u6E77',55889:'\u6E55',55890:'\u6E79',55891:'\u6E52',55892:'\u6E66',55893:'\u6E35',55894:'\u6E36',55895:'\u6E5A',55896:'\u7120',55897:'\u711E',55898:'\u712F',55899:'\u70FB',55900:'\u712E',55901:'\u7131',55902:'\u7123',55903:'\u7125',55904:'\u7122',55905:'\u7132',55906:'\u711F',55907:'\u7128',55908:'\u713A',55909:'\u711B',55910:'\u724B',55911:'\u725A',55912:'\u7288',55913:'\u7289',55914:'\u7286',55915:'\u7285',55916:'\u728B',55917:'\u7312',55918:'\u730B',55919:'\u7330',55920:'\u7322',55921:'\u7331',55922:'\u7333',55923:'\u7327',55924:'\u7332',55925:'\u732D',55926:'\u7326',55927:'\u7323',55928:'\u7335',55929:'\u730C',55930:'\u742E',55931:'\u742C',55932:'\u7430',55933:'\u742B',55934:'\u7416',55969:'\u741A',55970:'\u7421',55971:'\u742D',55972:'\u7431',55973:'\u7424',55974:'\u7423',55975:'\u741D',55976:'\u7429',55977:'\u7420',55978:'\u7432',55979:'\u74FB',55980:'\u752F',55981:'\u756F',55982:'\u756C',55983:'\u75E7',55984:'\u75DA',55985:'\u75E1',55986:'\u75E6',55987:'\u75DD',55988:'\u75DF',55989:'\u75E4',55990:'\u75D7',55991:'\u7695',55992:'\u7692',55993:'\u76DA',55994:'\u7746',55995:'\u7747',55996:'\u7744',55997:'\u774D',55998:'\u7745',55999:'\u774A',56000:'\u774E',56001:'\u774B',56002:'\u774C',56003:'\u77DE',56004:'\u77EC',56005:'\u7860',56006:'\u7864',56007:'\u7865',56008:'\u785C',56009:'\u786D',56010:'\u7871',56011:'\u786A',56012:'\u786E',56013:'\u7870',56014:'\u7869',56015:'\u7868',56016:'\u785E',56017:'\u7862',56018:'\u7974',56019:'\u7973',56020:'\u7972',56021:'\u7970',56022:'\u7A02',56023:'\u7A0A',56024:'\u7A03',56025:'\u7A0C',56026:'\u7A04',56027:'\u7A99',56028:'\u7AE6',56029:'\u7AE4',56030:'\u7B4A',56031:'\u7B3B',56032:'\u7B44',56033:'\u7B48',56034:'\u7B4C',56035:'\u7B4E',56036:'\u7B40',56037:'\u7B58',56038:'\u7B45',56039:'\u7CA2',56040:'\u7C9E',56041:'\u7CA8',56042:'\u7CA1',56043:'\u7D58',56044:'\u7D6F',56045:'\u7D63',56046:'\u7D53',56047:'\u7D56',56048:'\u7D67',56049:'\u7D6A',56050:'\u7D4F',56051:'\u7D6D',56052:'\u7D5C',56053:'\u7D6B',56054:'\u7D52',56055:'\u7D54',56056:'\u7D69',56057:'\u7D51',56058:'\u7D5F',56059:'\u7D4E',56060:'\u7F3E',56061:'\u7F3F',56062:'\u7F65',56128:'\u7F66',56129:'\u7FA2',56130:'\u7FA0',56131:'\u7FA1',56132:'\u7FD7',56133:'\u8051',56134:'\u804F',56135:'\u8050',56136:'\u80FE',56137:'\u80D4',56138:'\u8143',56139:'\u814A',56140:'\u8152',56141:'\u814F',56142:'\u8147',56143:'\u813D',56144:'\u814D',56145:'\u813A',56146:'\u81E6',56147:'\u81EE',56148:'\u81F7',56149:'\u81F8',56150:'\u81F9',56151:'\u8204',56152:'\u823C',56153:'\u823D',56154:'\u823F',56155:'\u8275',56156:'\u833B',56157:'\u83CF',56158:'\u83F9',56159:'\u8423',56160:'\u83C0',56161:'\u83E8',56162:'\u8412',56163:'\u83E7',56164:'\u83E4',56165:'\u83FC',56166:'\u83F6',56167:'\u8410',56168:'\u83C6',56169:'\u83C8',56170:'\u83EB',56171:'\u83E3',56172:'\u83BF',56173:'\u8401',56174:'\u83DD',56175:'\u83E5',56176:'\u83D8',56177:'\u83FF',56178:'\u83E1',56179:'\u83CB',56180:'\u83CE',56181:'\u83D6',56182:'\u83F5',56183:'\u83C9',56184:'\u8409',56185:'\u840F',56186:'\u83DE',56187:'\u8411',56188:'\u8406',56189:'\u83C2',56190:'\u83F3',56225:'\u83D5',56226:'\u83FA',56227:'\u83C7',56228:'\u83D1',56229:'\u83EA',56230:'\u8413',56231:'\u83C3',56232:'\u83EC',56233:'\u83EE',56234:'\u83C4',56235:'\u83FB',56236:'\u83D7',56237:'\u83E2',56238:'\u841B',56239:'\u83DB',56240:'\u83FE',56241:'\u86D8',56242:'\u86E2',56243:'\u86E6',56244:'\u86D3',56245:'\u86E3',56246:'\u86DA',56247:'\u86EA',56248:'\u86DD',56249:'\u86EB',56250:'\u86DC',56251:'\u86EC',56252:'\u86E9',56253:'\u86D7',56254:'\u86E8',56255:'\u86D1',56256:'\u8848',56257:'\u8856',56258:'\u8855',56259:'\u88BA',56260:'\u88D7',56261:'\u88B9',56262:'\u88B8',56263:'\u88C0',56264:'\u88BE',56265:'\u88B6',56266:'\u88BC',56267:'\u88B7',56268:'\u88BD',56269:'\u88B2',56270:'\u8901',56271:'\u88C9',56272:'\u8995',56273:'\u8998',56274:'\u8997',56275:'\u89DD',56276:'\u89DA',56277:'\u89DB',56278:'\u8A4E',56279:'\u8A4D',56280:'\u8A39',56281:'\u8A59',56282:'\u8A40',56283:'\u8A57',56284:'\u8A58',56285:'\u8A44',56286:'\u8A45',56287:'\u8A52',56288:'\u8A48',56289:'\u8A51',56290:'\u8A4A',56291:'\u8A4C',56292:'\u8A4F',56293:'\u8C5F',56294:'\u8C81',56295:'\u8C80',56296:'\u8CBA',56297:'\u8CBE',56298:'\u8CB0',56299:'\u8CB9',56300:'\u8CB5',56301:'\u8D84',56302:'\u8D80',56303:'\u8D89',56304:'\u8DD8',56305:'\u8DD3',56306:'\u8DCD',56307:'\u8DC7',56308:'\u8DD6',56309:'\u8DDC',56310:'\u8DCF',56311:'\u8DD5',56312:'\u8DD9',56313:'\u8DC8',56314:'\u8DD7',56315:'\u8DC5',56316:'\u8EEF',56317:'\u8EF7',56318:'\u8EFA',56384:'\u8EF9',56385:'\u8EE6',56386:'\u8EEE',56387:'\u8EE5',56388:'\u8EF5',56389:'\u8EE7',56390:'\u8EE8',56391:'\u8EF6',56392:'\u8EEB',56393:'\u8EF1',56394:'\u8EEC',56395:'\u8EF4',56396:'\u8EE9',56397:'\u902D',56398:'\u9034',56399:'\u902F',56400:'\u9106',56401:'\u912C',56402:'\u9104',56403:'\u90FF',56404:'\u90FC',56405:'\u9108',56406:'\u90F9',56407:'\u90FB',56408:'\u9101',56409:'\u9100',56410:'\u9107',56411:'\u9105',56412:'\u9103',56413:'\u9161',56414:'\u9164',56415:'\u915F',56416:'\u9162',56417:'\u9160',56418:'\u9201',56419:'\u920A',56420:'\u9225',56421:'\u9203',56422:'\u921A',56423:'\u9226',56424:'\u920F',56425:'\u920C',56426:'\u9200',56427:'\u9212',56428:'\u91FF',56429:'\u91FD',56430:'\u9206',56431:'\u9204',56432:'\u9227',56433:'\u9202',56434:'\u921C',56435:'\u9224',56436:'\u9219',56437:'\u9217',56438:'\u9205',56439:'\u9216',56440:'\u957B',56441:'\u958D',56442:'\u958C',56443:'\u9590',56444:'\u9687',56445:'\u967E',56446:'\u9688',56481:'\u9689',56482:'\u9683',56483:'\u9680',56484:'\u96C2',56485:'\u96C8',56486:'\u96C3',56487:'\u96F1',56488:'\u96F0',56489:'\u976C',56490:'\u9770',56491:'\u976E',56492:'\u9807',56493:'\u98A9',56494:'\u98EB',56495:'\u9CE6',56496:'\u9EF9',56497:'\u4E83',56498:'\u4E84',56499:'\u4EB6',56500:'\u50BD',56501:'\u50BF',56502:'\u50C6',56503:'\u50AE',56504:'\u50C4',56505:'\u50CA',56506:'\u50B4',56507:'\u50C8',56508:'\u50C2',56509:'\u50B0',56510:'\u50C1',56511:'\u50BA',56512:'\u50B1',56513:'\u50CB',56514:'\u50C9',56515:'\u50B6',56516:'\u50B8',56517:'\u51D7',56518:'\u527A',56519:'\u5278',56520:'\u527B',56521:'\u527C',56522:'\u55C3',56523:'\u55DB',56524:'\u55CC',56525:'\u55D0',56526:'\u55CB',56527:'\u55CA',56528:'\u55DD',56529:'\u55C0',56530:'\u55D4',56531:'\u55C4',56532:'\u55E9',56533:'\u55BF',56534:'\u55D2',56535:'\u558D',56536:'\u55CF',56537:'\u55D5',56538:'\u55E2',56539:'\u55D6',56540:'\u55C8',56541:'\u55F2',56542:'\u55CD',56543:'\u55D9',56544:'\u55C2',56545:'\u5714',56546:'\u5853',56547:'\u5868',56548:'\u5864',56549:'\u584F',56550:'\u584D',56551:'\u5849',56552:'\u586F',56553:'\u5855',56554:'\u584E',56555:'\u585D',56556:'\u5859',56557:'\u5865',56558:'\u585B',56559:'\u583D',56560:'\u5863',56561:'\u5871',56562:'\u58FC',56563:'\u5AC7',56564:'\u5AC4',56565:'\u5ACB',56566:'\u5ABA',56567:'\u5AB8',56568:'\u5AB1',56569:'\u5AB5',56570:'\u5AB0',56571:'\u5ABF',56572:'\u5AC8',56573:'\u5ABB',56574:'\u5AC6',56640:'\u5AB7',56641:'\u5AC0',56642:'\u5ACA',56643:'\u5AB4',56644:'\u5AB6',56645:'\u5ACD',56646:'\u5AB9',56647:'\u5A90',56648:'\u5BD6',56649:'\u5BD8',56650:'\u5BD9',56651:'\u5C1F',56652:'\u5C33',56653:'\u5D71',56654:'\u5D63',56655:'\u5D4A',56656:'\u5D65',56657:'\u5D72',56658:'\u5D6C',56659:'\u5D5E',56660:'\u5D68',56661:'\u5D67',56662:'\u5D62',56663:'\u5DF0',56664:'\u5E4F',56665:'\u5E4E',56666:'\u5E4A',56667:'\u5E4D',56668:'\u5E4B',56669:'\u5EC5',56670:'\u5ECC',56671:'\u5EC6',56672:'\u5ECB',56673:'\u5EC7',56674:'\u5F40',56675:'\u5FAF',56676:'\u5FAD',56677:'\u60F7',56678:'\u6149',56679:'\u614A',56680:'\u612B',56681:'\u6145',56682:'\u6136',56683:'\u6132',56684:'\u612E',56685:'\u6146',56686:'\u612F',56687:'\u614F',56688:'\u6129',56689:'\u6140',56690:'\u6220',56691:'\u9168',56692:'\u6223',56693:'\u6225',56694:'\u6224',56695:'\u63C5',56696:'\u63F1',56697:'\u63EB',56698:'\u6410',56699:'\u6412',56700:'\u6409',56701:'\u6420',56702:'\u6424',56737:'\u6433',56738:'\u6443',56739:'\u641F',56740:'\u6415',56741:'\u6418',56742:'\u6439',56743:'\u6437',56744:'\u6422',56745:'\u6423',56746:'\u640C',56747:'\u6426',56748:'\u6430',56749:'\u6428',56750:'\u6441',56751:'\u6435',56752:'\u642F',56753:'\u640A',56754:'\u641A',56755:'\u6440',56756:'\u6425',56757:'\u6427',56758:'\u640B',56759:'\u63E7',56760:'\u641B',56761:'\u642E',56762:'\u6421',56763:'\u640E',56764:'\u656F',56765:'\u6592',56766:'\u65D3',56767:'\u6686',56768:'\u668C',56769:'\u6695',56770:'\u6690',56771:'\u668B',56772:'\u668A',56773:'\u6699',56774:'\u6694',56775:'\u6678',56776:'\u6720',56777:'\u6966',56778:'\u695F',56779:'\u6938',56780:'\u694E',56781:'\u6962',56782:'\u6971',56783:'\u693F',56784:'\u6945',56785:'\u696A',56786:'\u6939',56787:'\u6942',56788:'\u6957',56789:'\u6959',56790:'\u697A',56791:'\u6948',56792:'\u6949',56793:'\u6935',56794:'\u696C',56795:'\u6933',56796:'\u693D',56797:'\u6965',56798:'\u68F0',56799:'\u6978',56800:'\u6934',56801:'\u6969',56802:'\u6940',56803:'\u696F',56804:'\u6944',56805:'\u6976',56806:'\u6958',56807:'\u6941',56808:'\u6974',56809:'\u694C',56810:'\u693B',56811:'\u694B',56812:'\u6937',56813:'\u695C',56814:'\u694F',56815:'\u6951',56816:'\u6932',56817:'\u6952',56818:'\u692F',56819:'\u697B',56820:'\u693C',56821:'\u6B46',56822:'\u6B45',56823:'\u6B43',56824:'\u6B42',56825:'\u6B48',56826:'\u6B41',56827:'\u6B9B',56828:'\uFA0D',56829:'\u6BFB',56830:'\u6BFC',56896:'\u6BF9',56897:'\u6BF7',56898:'\u6BF8',56899:'\u6E9B',56900:'\u6ED6',56901:'\u6EC8',56902:'\u6E8F',56903:'\u6EC0',56904:'\u6E9F',56905:'\u6E93',56906:'\u6E94',56907:'\u6EA0',56908:'\u6EB1',56909:'\u6EB9',56910:'\u6EC6',56911:'\u6ED2',56912:'\u6EBD',56913:'\u6EC1',56914:'\u6E9E',56915:'\u6EC9',56916:'\u6EB7',56917:'\u6EB0',56918:'\u6ECD',56919:'\u6EA6',56920:'\u6ECF',56921:'\u6EB2',56922:'\u6EBE',56923:'\u6EC3',56924:'\u6EDC',56925:'\u6ED8',56926:'\u6E99',56927:'\u6E92',56928:'\u6E8E',56929:'\u6E8D',56930:'\u6EA4',56931:'\u6EA1',56932:'\u6EBF',56933:'\u6EB3',56934:'\u6ED0',56935:'\u6ECA',56936:'\u6E97',56937:'\u6EAE',56938:'\u6EA3',56939:'\u7147',56940:'\u7154',56941:'\u7152',56942:'\u7163',56943:'\u7160',56944:'\u7141',56945:'\u715D',56946:'\u7162',56947:'\u7172',56948:'\u7178',56949:'\u716A',56950:'\u7161',56951:'\u7142',56952:'\u7158',56953:'\u7143',56954:'\u714B',56955:'\u7170',56956:'\u715F',56957:'\u7150',56958:'\u7153',56993:'\u7144',56994:'\u714D',56995:'\u715A',56996:'\u724F',56997:'\u728D',56998:'\u728C',56999:'\u7291',57000:'\u7290',57001:'\u728E',57002:'\u733C',57003:'\u7342',57004:'\u733B',57005:'\u733A',57006:'\u7340',57007:'\u734A',57008:'\u7349',57009:'\u7444',57010:'\u744A',57011:'\u744B',57012:'\u7452',57013:'\u7451',57014:'\u7457',57015:'\u7440',57016:'\u744F',57017:'\u7450',57018:'\u744E',57019:'\u7442',57020:'\u7446',57021:'\u744D',57022:'\u7454',57023:'\u74E1',57024:'\u74FF',57025:'\u74FE',57026:'\u74FD',57027:'\u751D',57028:'\u7579',57029:'\u7577',57030:'\u6983',57031:'\u75EF',57032:'\u760F',57033:'\u7603',57034:'\u75F7',57035:'\u75FE',57036:'\u75FC',57037:'\u75F9',57038:'\u75F8',57039:'\u7610',57040:'\u75FB',57041:'\u75F6',57042:'\u75ED',57043:'\u75F5',57044:'\u75FD',57045:'\u7699',57046:'\u76B5',57047:'\u76DD',57048:'\u7755',57049:'\u775F',57050:'\u7760',57051:'\u7752',57052:'\u7756',57053:'\u775A',57054:'\u7769',57055:'\u7767',57056:'\u7754',57057:'\u7759',57058:'\u776D',57059:'\u77E0',57060:'\u7887',57061:'\u789A',57062:'\u7894',57063:'\u788F',57064:'\u7884',57065:'\u7895',57066:'\u7885',57067:'\u7886',57068:'\u78A1',57069:'\u7883',57070:'\u7879',57071:'\u7899',57072:'\u7880',57073:'\u7896',57074:'\u787B',57075:'\u797C',57076:'\u7982',57077:'\u797D',57078:'\u7979',57079:'\u7A11',57080:'\u7A18',57081:'\u7A19',57082:'\u7A12',57083:'\u7A17',57084:'\u7A15',57085:'\u7A22',57086:'\u7A13',57152:'\u7A1B',57153:'\u7A10',57154:'\u7AA3',57155:'\u7AA2',57156:'\u7A9E',57157:'\u7AEB',57158:'\u7B66',57159:'\u7B64',57160:'\u7B6D',57161:'\u7B74',57162:'\u7B69',57163:'\u7B72',57164:'\u7B65',57165:'\u7B73',57166:'\u7B71',57167:'\u7B70',57168:'\u7B61',57169:'\u7B78',57170:'\u7B76',57171:'\u7B63',57172:'\u7CB2',57173:'\u7CB4',57174:'\u7CAF',57175:'\u7D88',57176:'\u7D86',57177:'\u7D80',57178:'\u7D8D',57179:'\u7D7F',57180:'\u7D85',57181:'\u7D7A',57182:'\u7D8E',57183:'\u7D7B',57184:'\u7D83',57185:'\u7D7C',57186:'\u7D8C',57187:'\u7D94',57188:'\u7D84',57189:'\u7D7D',57190:'\u7D92',57191:'\u7F6D',57192:'\u7F6B',57193:'\u7F67',57194:'\u7F68',57195:'\u7F6C',57196:'\u7FA6',57197:'\u7FA5',57198:'\u7FA7',57199:'\u7FDB',57200:'\u7FDC',57201:'\u8021',57202:'\u8164',57203:'\u8160',57204:'\u8177',57205:'\u815C',57206:'\u8169',57207:'\u815B',57208:'\u8162',57209:'\u8172',57210:'\u6721',57211:'\u815E',57212:'\u8176',57213:'\u8167',57214:'\u816F',57249:'\u8144',57250:'\u8161',57251:'\u821D',57252:'\u8249',57253:'\u8244',57254:'\u8240',57255:'\u8242',57256:'\u8245',57257:'\u84F1',57258:'\u843F',57259:'\u8456',57260:'\u8476',57261:'\u8479',57262:'\u848F',57263:'\u848D',57264:'\u8465',57265:'\u8451',57266:'\u8440',57267:'\u8486',57268:'\u8467',57269:'\u8430',57270:'\u844D',57271:'\u847D',57272:'\u845A',57273:'\u8459',57274:'\u8474',57275:'\u8473',57276:'\u845D',57277:'\u8507',57278:'\u845E',57279:'\u8437',57280:'\u843A',57281:'\u8434',57282:'\u847A',57283:'\u8443',57284:'\u8478',57285:'\u8432',57286:'\u8445',57287:'\u8429',57288:'\u83D9',57289:'\u844B',57290:'\u842F',57291:'\u8442',57292:'\u842D',57293:'\u845F',57294:'\u8470',57295:'\u8439',57296:'\u844E',57297:'\u844C',57298:'\u8452',57299:'\u846F',57300:'\u84C5',57301:'\u848E',57302:'\u843B',57303:'\u8447',57304:'\u8436',57305:'\u8433',57306:'\u8468',57307:'\u847E',57308:'\u8444',57309:'\u842B',57310:'\u8460',57311:'\u8454',57312:'\u846E',57313:'\u8450',57314:'\u870B',57315:'\u8704',57316:'\u86F7',57317:'\u870C',57318:'\u86FA',57319:'\u86D6',57320:'\u86F5',57321:'\u874D',57322:'\u86F8',57323:'\u870E',57324:'\u8709',57325:'\u8701',57326:'\u86F6',57327:'\u870D',57328:'\u8705',57329:'\u88D6',57330:'\u88CB',57331:'\u88CD',57332:'\u88CE',57333:'\u88DE',57334:'\u88DB',57335:'\u88DA',57336:'\u88CC',57337:'\u88D0',57338:'\u8985',57339:'\u899B',57340:'\u89DF',57341:'\u89E5',57342:'\u89E4',57408:'\u89E1',57409:'\u89E0',57410:'\u89E2',57411:'\u89DC',57412:'\u89E6',57413:'\u8A76',57414:'\u8A86',57415:'\u8A7F',57416:'\u8A61',57417:'\u8A3F',57418:'\u8A77',57419:'\u8A82',57420:'\u8A84',57421:'\u8A75',57422:'\u8A83',57423:'\u8A81',57424:'\u8A74',57425:'\u8A7A',57426:'\u8C3C',57427:'\u8C4B',57428:'\u8C4A',57429:'\u8C65',57430:'\u8C64',57431:'\u8C66',57432:'\u8C86',57433:'\u8C84',57434:'\u8C85',57435:'\u8CCC',57436:'\u8D68',57437:'\u8D69',57438:'\u8D91',57439:'\u8D8C',57440:'\u8D8E',57441:'\u8D8F',57442:'\u8D8D',57443:'\u8D93',57444:'\u8D94',57445:'\u8D90',57446:'\u8D92',57447:'\u8DF0',57448:'\u8DE0',57449:'\u8DEC',57450:'\u8DF1',57451:'\u8DEE',57452:'\u8DD0',57453:'\u8DE9',57454:'\u8DE3',57455:'\u8DE2',57456:'\u8DE7',57457:'\u8DF2',57458:'\u8DEB',57459:'\u8DF4',57460:'\u8F06',57461:'\u8EFF',57462:'\u8F01',57463:'\u8F00',57464:'\u8F05',57465:'\u8F07',57466:'\u8F08',57467:'\u8F02',57468:'\u8F0B',57469:'\u9052',57470:'\u903F',57505:'\u9044',57506:'\u9049',57507:'\u903D',57508:'\u9110',57509:'\u910D',57510:'\u910F',57511:'\u9111',57512:'\u9116',57513:'\u9114',57514:'\u910B',57515:'\u910E',57516:'\u916E',57517:'\u916F',57518:'\u9248',57519:'\u9252',57520:'\u9230',57521:'\u923A',57522:'\u9266',57523:'\u9233',57524:'\u9265',57525:'\u925E',57526:'\u9283',57527:'\u922E',57528:'\u924A',57529:'\u9246',57530:'\u926D',57531:'\u926C',57532:'\u924F',57533:'\u9260',57534:'\u9267',57535:'\u926F',57536:'\u9236',57537:'\u9261',57538:'\u9270',57539:'\u9231',57540:'\u9254',57541:'\u9263',57542:'\u9250',57543:'\u9272',57544:'\u924E',57545:'\u9253',57546:'\u924C',57547:'\u9256',57548:'\u9232',57549:'\u959F',57550:'\u959C',57551:'\u959E',57552:'\u959B',57553:'\u9692',57554:'\u9693',57555:'\u9691',57556:'\u9697',57557:'\u96CE',57558:'\u96FA',57559:'\u96FD',57560:'\u96F8',57561:'\u96F5',57562:'\u9773',57563:'\u9777',57564:'\u9778',57565:'\u9772',57566:'\u980F',57567:'\u980D',57568:'\u980E',57569:'\u98AC',57570:'\u98F6',57571:'\u98F9',57572:'\u99AF',57573:'\u99B2',57574:'\u99B0',57575:'\u99B5',57576:'\u9AAD',57577:'\u9AAB',57578:'\u9B5B',57579:'\u9CEA',57580:'\u9CED',57581:'\u9CE7',57582:'\u9E80',57583:'\u9EFD',57584:'\u50E6',57585:'\u50D4',57586:'\u50D7',57587:'\u50E8',57588:'\u50F3',57589:'\u50DB',57590:'\u50EA',57591:'\u50DD',57592:'\u50E4',57593:'\u50D3',57594:'\u50EC',57595:'\u50F0',57596:'\u50EF',57597:'\u50E3',57598:'\u50E0',57664:'\u51D8',57665:'\u5280',57666:'\u5281',57667:'\u52E9',57668:'\u52EB',57669:'\u5330',57670:'\u53AC',57671:'\u5627',57672:'\u5615',57673:'\u560C',57674:'\u5612',57675:'\u55FC',57676:'\u560F',57677:'\u561C',57678:'\u5601',57679:'\u5613',57680:'\u5602',57681:'\u55FA',57682:'\u561D',57683:'\u5604',57684:'\u55FF',57685:'\u55F9',57686:'\u5889',57687:'\u587C',57688:'\u5890',57689:'\u5898',57690:'\u5886',57691:'\u5881',57692:'\u587F',57693:'\u5874',57694:'\u588B',57695:'\u587A',57696:'\u5887',57697:'\u5891',57698:'\u588E',57699:'\u5876',57700:'\u5882',57701:'\u5888',57702:'\u587B',57703:'\u5894',57704:'\u588F',57705:'\u58FE',57706:'\u596B',57707:'\u5ADC',57708:'\u5AEE',57709:'\u5AE5',57710:'\u5AD5',57711:'\u5AEA',57712:'\u5ADA',57713:'\u5AED',57714:'\u5AEB',57715:'\u5AF3',57716:'\u5AE2',57717:'\u5AE0',57718:'\u5ADB',57719:'\u5AEC',57720:'\u5ADE',57721:'\u5ADD',57722:'\u5AD9',57723:'\u5AE8',57724:'\u5ADF',57725:'\u5B77',57726:'\u5BE0',57761:'\u5BE3',57762:'\u5C63',57763:'\u5D82',57764:'\u5D80',57765:'\u5D7D',57766:'\u5D86',57767:'\u5D7A',57768:'\u5D81',57769:'\u5D77',57770:'\u5D8A',57771:'\u5D89',57772:'\u5D88',57773:'\u5D7E',57774:'\u5D7C',57775:'\u5D8D',57776:'\u5D79',57777:'\u5D7F',57778:'\u5E58',57779:'\u5E59',57780:'\u5E53',57781:'\u5ED8',57782:'\u5ED1',57783:'\u5ED7',57784:'\u5ECE',57785:'\u5EDC',57786:'\u5ED5',57787:'\u5ED9',57788:'\u5ED2',57789:'\u5ED4',57790:'\u5F44',57791:'\u5F43',57792:'\u5F6F',57793:'\u5FB6',57794:'\u612C',57795:'\u6128',57796:'\u6141',57797:'\u615E',57798:'\u6171',57799:'\u6173',57800:'\u6152',57801:'\u6153',57802:'\u6172',57803:'\u616C',57804:'\u6180',57805:'\u6174',57806:'\u6154',57807:'\u617A',57808:'\u615B',57809:'\u6165',57810:'\u613B',57811:'\u616A',57812:'\u6161',57813:'\u6156',57814:'\u6229',57815:'\u6227',57816:'\u622B',57817:'\u642B',57818:'\u644D',57819:'\u645B',57820:'\u645D',57821:'\u6474',57822:'\u6476',57823:'\u6472',57824:'\u6473',57825:'\u647D',57826:'\u6475',57827:'\u6466',57828:'\u64A6',57829:'\u644E',57830:'\u6482',57831:'\u645E',57832:'\u645C',57833:'\u644B',57834:'\u6453',57835:'\u6460',57836:'\u6450',57837:'\u647F',57838:'\u643F',57839:'\u646C',57840:'\u646B',57841:'\u6459',57842:'\u6465',57843:'\u6477',57844:'\u6573',57845:'\u65A0',57846:'\u66A1',57847:'\u66A0',57848:'\u669F',57849:'\u6705',57850:'\u6704',57851:'\u6722',57852:'\u69B1',57853:'\u69B6',57854:'\u69C9',57920:'\u69A0',57921:'\u69CE',57922:'\u6996',57923:'\u69B0',57924:'\u69AC',57925:'\u69BC',57926:'\u6991',57927:'\u6999',57928:'\u698E',57929:'\u69A7',57930:'\u698D',57931:'\u69A9',57932:'\u69BE',57933:'\u69AF',57934:'\u69BF',57935:'\u69C4',57936:'\u69BD',57937:'\u69A4',57938:'\u69D4',57939:'\u69B9',57940:'\u69CA',57941:'\u699A',57942:'\u69CF',57943:'\u69B3',57944:'\u6993',57945:'\u69AA',57946:'\u69A1',57947:'\u699E',57948:'\u69D9',57949:'\u6997',57950:'\u6990',57951:'\u69C2',57952:'\u69B5',57953:'\u69A5',57954:'\u69C6',57955:'\u6B4A',57956:'\u6B4D',57957:'\u6B4B',57958:'\u6B9E',57959:'\u6B9F',57960:'\u6BA0',57961:'\u6BC3',57962:'\u6BC4',57963:'\u6BFE',57964:'\u6ECE',57965:'\u6EF5',57966:'\u6EF1',57967:'\u6F03',57968:'\u6F25',57969:'\u6EF8',57970:'\u6F37',57971:'\u6EFB',57972:'\u6F2E',57973:'\u6F09',57974:'\u6F4E',57975:'\u6F19',57976:'\u6F1A',57977:'\u6F27',57978:'\u6F18',57979:'\u6F3B',57980:'\u6F12',57981:'\u6EED',57982:'\u6F0A',58017:'\u6F36',58018:'\u6F73',58019:'\u6EF9',58020:'\u6EEE',58021:'\u6F2D',58022:'\u6F40',58023:'\u6F30',58024:'\u6F3C',58025:'\u6F35',58026:'\u6EEB',58027:'\u6F07',58028:'\u6F0E',58029:'\u6F43',58030:'\u6F05',58031:'\u6EFD',58032:'\u6EF6',58033:'\u6F39',58034:'\u6F1C',58035:'\u6EFC',58036:'\u6F3A',58037:'\u6F1F',58038:'\u6F0D',58039:'\u6F1E',58040:'\u6F08',58041:'\u6F21',58042:'\u7187',58043:'\u7190',58044:'\u7189',58045:'\u7180',58046:'\u7185',58047:'\u7182',58048:'\u718F',58049:'\u717B',58050:'\u7186',58051:'\u7181',58052:'\u7197',58053:'\u7244',58054:'\u7253',58055:'\u7297',58056:'\u7295',58057:'\u7293',58058:'\u7343',58059:'\u734D',58060:'\u7351',58061:'\u734C',58062:'\u7462',58063:'\u7473',58064:'\u7471',58065:'\u7475',58066:'\u7472',58067:'\u7467',58068:'\u746E',58069:'\u7500',58070:'\u7502',58071:'\u7503',58072:'\u757D',58073:'\u7590',58074:'\u7616',58075:'\u7608',58076:'\u760C',58077:'\u7615',58078:'\u7611',58079:'\u760A',58080:'\u7614',58081:'\u76B8',58082:'\u7781',58083:'\u777C',58084:'\u7785',58085:'\u7782',58086:'\u776E',58087:'\u7780',58088:'\u776F',58089:'\u777E',58090:'\u7783',58091:'\u78B2',58092:'\u78AA',58093:'\u78B4',58094:'\u78AD',58095:'\u78A8',58096:'\u787E',58097:'\u78AB',58098:'\u789E',58099:'\u78A5',58100:'\u78A0',58101:'\u78AC',58102:'\u78A2',58103:'\u78A4',58104:'\u7998',58105:'\u798A',58106:'\u798B',58107:'\u7996',58108:'\u7995',58109:'\u7994',58110:'\u7993',58176:'\u7997',58177:'\u7988',58178:'\u7992',58179:'\u7990',58180:'\u7A2B',58181:'\u7A4A',58182:'\u7A30',58183:'\u7A2F',58184:'\u7A28',58185:'\u7A26',58186:'\u7AA8',58187:'\u7AAB',58188:'\u7AAC',58189:'\u7AEE',58190:'\u7B88',58191:'\u7B9C',58192:'\u7B8A',58193:'\u7B91',58194:'\u7B90',58195:'\u7B96',58196:'\u7B8D',58197:'\u7B8C',58198:'\u7B9B',58199:'\u7B8E',58200:'\u7B85',58201:'\u7B98',58202:'\u5284',58203:'\u7B99',58204:'\u7BA4',58205:'\u7B82',58206:'\u7CBB',58207:'\u7CBF',58208:'\u7CBC',58209:'\u7CBA',58210:'\u7DA7',58211:'\u7DB7',58212:'\u7DC2',58213:'\u7DA3',58214:'\u7DAA',58215:'\u7DC1',58216:'\u7DC0',58217:'\u7DC5',58218:'\u7D9D',58219:'\u7DCE',58220:'\u7DC4',58221:'\u7DC6',58222:'\u7DCB',58223:'\u7DCC',58224:'\u7DAF',58225:'\u7DB9',58226:'\u7D96',58227:'\u7DBC',58228:'\u7D9F',58229:'\u7DA6',58230:'\u7DAE',58231:'\u7DA9',58232:'\u7DA1',58233:'\u7DC9',58234:'\u7F73',58235:'\u7FE2',58236:'\u7FE3',58237:'\u7FE5',58238:'\u7FDE',58273:'\u8024',58274:'\u805D',58275:'\u805C',58276:'\u8189',58277:'\u8186',58278:'\u8183',58279:'\u8187',58280:'\u818D',58281:'\u818C',58282:'\u818B',58283:'\u8215',58284:'\u8497',58285:'\u84A4',58286:'\u84A1',58287:'\u849F',58288:'\u84BA',58289:'\u84CE',58290:'\u84C2',58291:'\u84AC',58292:'\u84AE',58293:'\u84AB',58294:'\u84B9',58295:'\u84B4',58296:'\u84C1',58297:'\u84CD',58298:'\u84AA',58299:'\u849A',58300:'\u84B1',58301:'\u84D0',58302:'\u849D',58303:'\u84A7',58304:'\u84BB',58305:'\u84A2',58306:'\u8494',58307:'\u84C7',58308:'\u84CC',58309:'\u849B',58310:'\u84A9',58311:'\u84AF',58312:'\u84A8',58313:'\u84D6',58314:'\u8498',58315:'\u84B6',58316:'\u84CF',58317:'\u84A0',58318:'\u84D7',58319:'\u84D4',58320:'\u84D2',58321:'\u84DB',58322:'\u84B0',58323:'\u8491',58324:'\u8661',58325:'\u8733',58326:'\u8723',58327:'\u8728',58328:'\u876B',58329:'\u8740',58330:'\u872E',58331:'\u871E',58332:'\u8721',58333:'\u8719',58334:'\u871B',58335:'\u8743',58336:'\u872C',58337:'\u8741',58338:'\u873E',58339:'\u8746',58340:'\u8720',58341:'\u8732',58342:'\u872A',58343:'\u872D',58344:'\u873C',58345:'\u8712',58346:'\u873A',58347:'\u8731',58348:'\u8735',58349:'\u8742',58350:'\u8726',58351:'\u8727',58352:'\u8738',58353:'\u8724',58354:'\u871A',58355:'\u8730',58356:'\u8711',58357:'\u88F7',58358:'\u88E7',58359:'\u88F1',58360:'\u88F2',58361:'\u88FA',58362:'\u88FE',58363:'\u88EE',58364:'\u88FC',58365:'\u88F6',58366:'\u88FB',58432:'\u88F0',58433:'\u88EC',58434:'\u88EB',58435:'\u899D',58436:'\u89A1',58437:'\u899F',58438:'\u899E',58439:'\u89E9',58440:'\u89EB',58441:'\u89E8',58442:'\u8AAB',58443:'\u8A99',58444:'\u8A8B',58445:'\u8A92',58446:'\u8A8F',58447:'\u8A96',58448:'\u8C3D',58449:'\u8C68',58450:'\u8C69',58451:'\u8CD5',58452:'\u8CCF',58453:'\u8CD7',58454:'\u8D96',58455:'\u8E09',58456:'\u8E02',58457:'\u8DFF',58458:'\u8E0D',58459:'\u8DFD',58460:'\u8E0A',58461:'\u8E03',58462:'\u8E07',58463:'\u8E06',58464:'\u8E05',58465:'\u8DFE',58466:'\u8E00',58467:'\u8E04',58468:'\u8F10',58469:'\u8F11',58470:'\u8F0E',58471:'\u8F0D',58472:'\u9123',58473:'\u911C',58474:'\u9120',58475:'\u9122',58476:'\u911F',58477:'\u911D',58478:'\u911A',58479:'\u9124',58480:'\u9121',58481:'\u911B',58482:'\u917A',58483:'\u9172',58484:'\u9179',58485:'\u9173',58486:'\u92A5',58487:'\u92A4',58488:'\u9276',58489:'\u929B',58490:'\u927A',58491:'\u92A0',58492:'\u9294',58493:'\u92AA',58494:'\u928D',58529:'\u92A6',58530:'\u929A',58531:'\u92AB',58532:'\u9279',58533:'\u9297',58534:'\u927F',58535:'\u92A3',58536:'\u92EE',58537:'\u928E',58538:'\u9282',58539:'\u9295',58540:'\u92A2',58541:'\u927D',58542:'\u9288',58543:'\u92A1',58544:'\u928A',58545:'\u9286',58546:'\u928C',58547:'\u9299',58548:'\u92A7',58549:'\u927E',58550:'\u9287',58551:'\u92A9',58552:'\u929D',58553:'\u928B',58554:'\u922D',58555:'\u969E',58556:'\u96A1',58557:'\u96FF',58558:'\u9758',58559:'\u977D',58560:'\u977A',58561:'\u977E',58562:'\u9783',58563:'\u9780',58564:'\u9782',58565:'\u977B',58566:'\u9784',58567:'\u9781',58568:'\u977F',58569:'\u97CE',58570:'\u97CD',58571:'\u9816',58572:'\u98AD',58573:'\u98AE',58574:'\u9902',58575:'\u9900',58576:'\u9907',58577:'\u999D',58578:'\u999C',58579:'\u99C3',58580:'\u99B9',58581:'\u99BB',58582:'\u99BA',58583:'\u99C2',58584:'\u99BD',58585:'\u99C7',58586:'\u9AB1',58587:'\u9AE3',58588:'\u9AE7',58589:'\u9B3E',58590:'\u9B3F',58591:'\u9B60',58592:'\u9B61',58593:'\u9B5F',58594:'\u9CF1',58595:'\u9CF2',58596:'\u9CF5',58597:'\u9EA7',58598:'\u50FF',58599:'\u5103',58600:'\u5130',58601:'\u50F8',58602:'\u5106',58603:'\u5107',58604:'\u50F6',58605:'\u50FE',58606:'\u510B',58607:'\u510C',58608:'\u50FD',58609:'\u510A',58610:'\u528B',58611:'\u528C',58612:'\u52F1',58613:'\u52EF',58614:'\u5648',58615:'\u5642',58616:'\u564C',58617:'\u5635',58618:'\u5641',58619:'\u564A',58620:'\u5649',58621:'\u5646',58622:'\u5658',58688:'\u565A',58689:'\u5640',58690:'\u5633',58691:'\u563D',58692:'\u562C',58693:'\u563E',58694:'\u5638',58695:'\u562A',58696:'\u563A',58697:'\u571A',58698:'\u58AB',58699:'\u589D',58700:'\u58B1',58701:'\u58A0',58702:'\u58A3',58703:'\u58AF',58704:'\u58AC',58705:'\u58A5',58706:'\u58A1',58707:'\u58FF',58708:'\u5AFF',58709:'\u5AF4',58710:'\u5AFD',58711:'\u5AF7',58712:'\u5AF6',58713:'\u5B03',58714:'\u5AF8',58715:'\u5B02',58716:'\u5AF9',58717:'\u5B01',58718:'\u5B07',58719:'\u5B05',58720:'\u5B0F',58721:'\u5C67',58722:'\u5D99',58723:'\u5D97',58724:'\u5D9F',58725:'\u5D92',58726:'\u5DA2',58727:'\u5D93',58728:'\u5D95',58729:'\u5DA0',58730:'\u5D9C',58731:'\u5DA1',58732:'\u5D9A',58733:'\u5D9E',58734:'\u5E69',58735:'\u5E5D',58736:'\u5E60',58737:'\u5E5C',58738:'\u7DF3',58739:'\u5EDB',58740:'\u5EDE',58741:'\u5EE1',58742:'\u5F49',58743:'\u5FB2',58744:'\u618B',58745:'\u6183',58746:'\u6179',58747:'\u61B1',58748:'\u61B0',58749:'\u61A2',58750:'\u6189',58785:'\u619B',58786:'\u6193',58787:'\u61AF',58788:'\u61AD',58789:'\u619F',58790:'\u6192',58791:'\u61AA',58792:'\u61A1',58793:'\u618D',58794:'\u6166',58795:'\u61B3',58796:'\u622D',58797:'\u646E',58798:'\u6470',58799:'\u6496',58800:'\u64A0',58801:'\u6485',58802:'\u6497',58803:'\u649C',58804:'\u648F',58805:'\u648B',58806:'\u648A',58807:'\u648C',58808:'\u64A3',58809:'\u649F',58810:'\u6468',58811:'\u64B1',58812:'\u6498',58813:'\u6576',58814:'\u657A',58815:'\u6579',58816:'\u657B',58817:'\u65B2',58818:'\u65B3',58819:'\u66B5',58820:'\u66B0',58821:'\u66A9',58822:'\u66B2',58823:'\u66B7',58824:'\u66AA',58825:'\u66AF',58826:'\u6A00',58827:'\u6A06',58828:'\u6A17',58829:'\u69E5',58830:'\u69F8',58831:'\u6A15',58832:'\u69F1',58833:'\u69E4',58834:'\u6A20',58835:'\u69FF',58836:'\u69EC',58837:'\u69E2',58838:'\u6A1B',58839:'\u6A1D',58840:'\u69FE',58841:'\u6A27',58842:'\u69F2',58843:'\u69EE',58844:'\u6A14',58845:'\u69F7',58846:'\u69E7',58847:'\u6A40',58848:'\u6A08',58849:'\u69E6',58850:'\u69FB',58851:'\u6A0D',58852:'\u69FC',58853:'\u69EB',58854:'\u6A09',58855:'\u6A04',58856:'\u6A18',58857:'\u6A25',58858:'\u6A0F',58859:'\u69F6',58860:'\u6A26',58861:'\u6A07',58862:'\u69F4',58863:'\u6A16',58864:'\u6B51',58865:'\u6BA5',58866:'\u6BA3',58867:'\u6BA2',58868:'\u6BA6',58869:'\u6C01',58870:'\u6C00',58871:'\u6BFF',58872:'\u6C02',58873:'\u6F41',58874:'\u6F26',58875:'\u6F7E',58876:'\u6F87',58877:'\u6FC6',58878:'\u6F92',58944:'\u6F8D',58945:'\u6F89',58946:'\u6F8C',58947:'\u6F62',58948:'\u6F4F',58949:'\u6F85',58950:'\u6F5A',58951:'\u6F96',58952:'\u6F76',58953:'\u6F6C',58954:'\u6F82',58955:'\u6F55',58956:'\u6F72',58957:'\u6F52',58958:'\u6F50',58959:'\u6F57',58960:'\u6F94',58961:'\u6F93',58962:'\u6F5D',58963:'\u6F00',58964:'\u6F61',58965:'\u6F6B',58966:'\u6F7D',58967:'\u6F67',58968:'\u6F90',58969:'\u6F53',58970:'\u6F8B',58971:'\u6F69',58972:'\u6F7F',58973:'\u6F95',58974:'\u6F63',58975:'\u6F77',58976:'\u6F6A',58977:'\u6F7B',58978:'\u71B2',58979:'\u71AF',58980:'\u719B',58981:'\u71B0',58982:'\u71A0',58983:'\u719A',58984:'\u71A9',58985:'\u71B5',58986:'\u719D',58987:'\u71A5',58988:'\u719E',58989:'\u71A4',58990:'\u71A1',58991:'\u71AA',58992:'\u719C',58993:'\u71A7',58994:'\u71B3',58995:'\u7298',58996:'\u729A',58997:'\u7358',58998:'\u7352',58999:'\u735E',59000:'\u735F',59001:'\u7360',59002:'\u735D',59003:'\u735B',59004:'\u7361',59005:'\u735A',59006:'\u7359',59041:'\u7362',59042:'\u7487',59043:'\u7489',59044:'\u748A',59045:'\u7486',59046:'\u7481',59047:'\u747D',59048:'\u7485',59049:'\u7488',59050:'\u747C',59051:'\u7479',59052:'\u7508',59053:'\u7507',59054:'\u757E',59055:'\u7625',59056:'\u761E',59057:'\u7619',59058:'\u761D',59059:'\u761C',59060:'\u7623',59061:'\u761A',59062:'\u7628',59063:'\u761B',59064:'\u769C',59065:'\u769D',59066:'\u769E',59067:'\u769B',59068:'\u778D',59069:'\u778F',59070:'\u7789',59071:'\u7788',59072:'\u78CD',59073:'\u78BB',59074:'\u78CF',59075:'\u78CC',59076:'\u78D1',59077:'\u78CE',59078:'\u78D4',59079:'\u78C8',59080:'\u78C3',59081:'\u78C4',59082:'\u78C9',59083:'\u799A',59084:'\u79A1',59085:'\u79A0',59086:'\u799C',59087:'\u79A2',59088:'\u799B',59089:'\u6B76',59090:'\u7A39',59091:'\u7AB2',59092:'\u7AB4',59093:'\u7AB3',59094:'\u7BB7',59095:'\u7BCB',59096:'\u7BBE',59097:'\u7BAC',59098:'\u7BCE',59099:'\u7BAF',59100:'\u7BB9',59101:'\u7BCA',59102:'\u7BB5',59103:'\u7CC5',59104:'\u7CC8',59105:'\u7CCC',59106:'\u7CCB',59107:'\u7DF7',59108:'\u7DDB',59109:'\u7DEA',59110:'\u7DE7',59111:'\u7DD7',59112:'\u7DE1',59113:'\u7E03',59114:'\u7DFA',59115:'\u7DE6',59116:'\u7DF6',59117:'\u7DF1',59118:'\u7DF0',59119:'\u7DEE',59120:'\u7DDF',59121:'\u7F76',59122:'\u7FAC',59123:'\u7FB0',59124:'\u7FAD',59125:'\u7FED',59126:'\u7FEB',59127:'\u7FEA',59128:'\u7FEC',59129:'\u7FE6',59130:'\u7FE8',59131:'\u8064',59132:'\u8067',59133:'\u81A3',59134:'\u819F',59200:'\u819E',59201:'\u8195',59202:'\u81A2',59203:'\u8199',59204:'\u8197',59205:'\u8216',59206:'\u824F',59207:'\u8253',59208:'\u8252',59209:'\u8250',59210:'\u824E',59211:'\u8251',59212:'\u8524',59213:'\u853B',59214:'\u850F',59215:'\u8500',59216:'\u8529',59217:'\u850E',59218:'\u8509',59219:'\u850D',59220:'\u851F',59221:'\u850A',59222:'\u8527',59223:'\u851C',59224:'\u84FB',59225:'\u852B',59226:'\u84FA',59227:'\u8508',59228:'\u850C',59229:'\u84F4',59230:'\u852A',59231:'\u84F2',59232:'\u8515',59233:'\u84F7',59234:'\u84EB',59235:'\u84F3',59236:'\u84FC',59237:'\u8512',59238:'\u84EA',59239:'\u84E9',59240:'\u8516',59241:'\u84FE',59242:'\u8528',59243:'\u851D',59244:'\u852E',59245:'\u8502',59246:'\u84FD',59247:'\u851E',59248:'\u84F6',59249:'\u8531',59250:'\u8526',59251:'\u84E7',59252:'\u84E8',59253:'\u84F0',59254:'\u84EF',59255:'\u84F9',59256:'\u8518',59257:'\u8520',59258:'\u8530',59259:'\u850B',59260:'\u8519',59261:'\u852F',59262:'\u8662',59297:'\u8756',59298:'\u8763',59299:'\u8764',59300:'\u8777',59301:'\u87E1',59302:'\u8773',59303:'\u8758',59304:'\u8754',59305:'\u875B',59306:'\u8752',59307:'\u8761',59308:'\u875A',59309:'\u8751',59310:'\u875E',59311:'\u876D',59312:'\u876A',59313:'\u8750',59314:'\u874E',59315:'\u875F',59316:'\u875D',59317:'\u876F',59318:'\u876C',59319:'\u877A',59320:'\u876E',59321:'\u875C',59322:'\u8765',59323:'\u874F',59324:'\u877B',59325:'\u8775',59326:'\u8762',59327:'\u8767',59328:'\u8769',59329:'\u885A',59330:'\u8905',59331:'\u890C',59332:'\u8914',59333:'\u890B',59334:'\u8917',59335:'\u8918',59336:'\u8919',59337:'\u8906',59338:'\u8916',59339:'\u8911',59340:'\u890E',59341:'\u8909',59342:'\u89A2',59343:'\u89A4',59344:'\u89A3',59345:'\u89ED',59346:'\u89F0',59347:'\u89EC',59348:'\u8ACF',59349:'\u8AC6',59350:'\u8AB8',59351:'\u8AD3',59352:'\u8AD1',59353:'\u8AD4',59354:'\u8AD5',59355:'\u8ABB',59356:'\u8AD7',59357:'\u8ABE',59358:'\u8AC0',59359:'\u8AC5',59360:'\u8AD8',59361:'\u8AC3',59362:'\u8ABA',59363:'\u8ABD',59364:'\u8AD9',59365:'\u8C3E',59366:'\u8C4D',59367:'\u8C8F',59368:'\u8CE5',59369:'\u8CDF',59370:'\u8CD9',59371:'\u8CE8',59372:'\u8CDA',59373:'\u8CDD',59374:'\u8CE7',59375:'\u8DA0',59376:'\u8D9C',59377:'\u8DA1',59378:'\u8D9B',59379:'\u8E20',59380:'\u8E23',59381:'\u8E25',59382:'\u8E24',59383:'\u8E2E',59384:'\u8E15',59385:'\u8E1B',59386:'\u8E16',59387:'\u8E11',59388:'\u8E19',59389:'\u8E26',59390:'\u8E27',59456:'\u8E14',59457:'\u8E12',59458:'\u8E18',59459:'\u8E13',59460:'\u8E1C',59461:'\u8E17',59462:'\u8E1A',59463:'\u8F2C',59464:'\u8F24',59465:'\u8F18',59466:'\u8F1A',59467:'\u8F20',59468:'\u8F23',59469:'\u8F16',59470:'\u8F17',59471:'\u9073',59472:'\u9070',59473:'\u906F',59474:'\u9067',59475:'\u906B',59476:'\u912F',59477:'\u912B',59478:'\u9129',59479:'\u912A',59480:'\u9132',59481:'\u9126',59482:'\u912E',59483:'\u9185',59484:'\u9186',59485:'\u918A',59486:'\u9181',59487:'\u9182',59488:'\u9184',59489:'\u9180',59490:'\u92D0',59491:'\u92C3',59492:'\u92C4',59493:'\u92C0',59494:'\u92D9',59495:'\u92B6',59496:'\u92CF',59497:'\u92F1',59498:'\u92DF',59499:'\u92D8',59500:'\u92E9',59501:'\u92D7',59502:'\u92DD',59503:'\u92CC',59504:'\u92EF',59505:'\u92C2',59506:'\u92E8',59507:'\u92CA',59508:'\u92C8',59509:'\u92CE',59510:'\u92E6',59511:'\u92CD',59512:'\u92D5',59513:'\u92C9',59514:'\u92E0',59515:'\u92DE',59516:'\u92E7',59517:'\u92D1',59518:'\u92D3',59553:'\u92B5',59554:'\u92E1',59555:'\u92C6',59556:'\u92B4',59557:'\u957C',59558:'\u95AC',59559:'\u95AB',59560:'\u95AE',59561:'\u95B0',59562:'\u96A4',59563:'\u96A2',59564:'\u96D3',59565:'\u9705',59566:'\u9708',59567:'\u9702',59568:'\u975A',59569:'\u978A',59570:'\u978E',59571:'\u9788',59572:'\u97D0',59573:'\u97CF',59574:'\u981E',59575:'\u981D',59576:'\u9826',59577:'\u9829',59578:'\u9828',59579:'\u9820',59580:'\u981B',59581:'\u9827',59582:'\u98B2',59583:'\u9908',59584:'\u98FA',59585:'\u9911',59586:'\u9914',59587:'\u9916',59588:'\u9917',59589:'\u9915',59590:'\u99DC',59591:'\u99CD',59592:'\u99CF',59593:'\u99D3',59594:'\u99D4',59595:'\u99CE',59596:'\u99C9',59597:'\u99D6',59598:'\u99D8',59599:'\u99CB',59600:'\u99D7',59601:'\u99CC',59602:'\u9AB3',59603:'\u9AEC',59604:'\u9AEB',59605:'\u9AF3',59606:'\u9AF2',59607:'\u9AF1',59608:'\u9B46',59609:'\u9B43',59610:'\u9B67',59611:'\u9B74',59612:'\u9B71',59613:'\u9B66',59614:'\u9B76',59615:'\u9B75',59616:'\u9B70',59617:'\u9B68',59618:'\u9B64',59619:'\u9B6C',59620:'\u9CFC',59621:'\u9CFA',59622:'\u9CFD',59623:'\u9CFF',59624:'\u9CF7',59625:'\u9D07',59626:'\u9D00',59627:'\u9CF9',59628:'\u9CFB',59629:'\u9D08',59630:'\u9D05',59631:'\u9D04',59632:'\u9E83',59633:'\u9ED3',59634:'\u9F0F',59635:'\u9F10',59636:'\u511C',59637:'\u5113',59638:'\u5117',59639:'\u511A',59640:'\u5111',59641:'\u51DE',59642:'\u5334',59643:'\u53E1',59644:'\u5670',59645:'\u5660',59646:'\u566E',59712:'\u5673',59713:'\u5666',59714:'\u5663',59715:'\u566D',59716:'\u5672',59717:'\u565E',59718:'\u5677',59719:'\u571C',59720:'\u571B',59721:'\u58C8',59722:'\u58BD',59723:'\u58C9',59724:'\u58BF',59725:'\u58BA',59726:'\u58C2',59727:'\u58BC',59728:'\u58C6',59729:'\u5B17',59730:'\u5B19',59731:'\u5B1B',59732:'\u5B21',59733:'\u5B14',59734:'\u5B13',59735:'\u5B10',59736:'\u5B16',59737:'\u5B28',59738:'\u5B1A',59739:'\u5B20',59740:'\u5B1E',59741:'\u5BEF',59742:'\u5DAC',59743:'\u5DB1',59744:'\u5DA9',59745:'\u5DA7',59746:'\u5DB5',59747:'\u5DB0',59748:'\u5DAE',59749:'\u5DAA',59750:'\u5DA8',59751:'\u5DB2',59752:'\u5DAD',59753:'\u5DAF',59754:'\u5DB4',59755:'\u5E67',59756:'\u5E68',59757:'\u5E66',59758:'\u5E6F',59759:'\u5EE9',59760:'\u5EE7',59761:'\u5EE6',59762:'\u5EE8',59763:'\u5EE5',59764:'\u5F4B',59765:'\u5FBC',59766:'\u619D',59767:'\u61A8',59768:'\u6196',59769:'\u61C5',59770:'\u61B4',59771:'\u61C6',59772:'\u61C1',59773:'\u61CC',59774:'\u61BA',59809:'\u61BF',59810:'\u61B8',59811:'\u618C',59812:'\u64D7',59813:'\u64D6',59814:'\u64D0',59815:'\u64CF',59816:'\u64C9',59817:'\u64BD',59818:'\u6489',59819:'\u64C3',59820:'\u64DB',59821:'\u64F3',59822:'\u64D9',59823:'\u6533',59824:'\u657F',59825:'\u657C',59826:'\u65A2',59827:'\u66C8',59828:'\u66BE',59829:'\u66C0',59830:'\u66CA',59831:'\u66CB',59832:'\u66CF',59833:'\u66BD',59834:'\u66BB',59835:'\u66BA',59836:'\u66CC',59837:'\u6723',59838:'\u6A34',59839:'\u6A66',59840:'\u6A49',59841:'\u6A67',59842:'\u6A32',59843:'\u6A68',59844:'\u6A3E',59845:'\u6A5D',59846:'\u6A6D',59847:'\u6A76',59848:'\u6A5B',59849:'\u6A51',59850:'\u6A28',59851:'\u6A5A',59852:'\u6A3B',59853:'\u6A3F',59854:'\u6A41',59855:'\u6A6A',59856:'\u6A64',59857:'\u6A50',59858:'\u6A4F',59859:'\u6A54',59860:'\u6A6F',59861:'\u6A69',59862:'\u6A60',59863:'\u6A3C',59864:'\u6A5E',59865:'\u6A56',59866:'\u6A55',59867:'\u6A4D',59868:'\u6A4E',59869:'\u6A46',59870:'\u6B55',59871:'\u6B54',59872:'\u6B56',59873:'\u6BA7',59874:'\u6BAA',59875:'\u6BAB',59876:'\u6BC8',59877:'\u6BC7',59878:'\u6C04',59879:'\u6C03',59880:'\u6C06',59881:'\u6FAD',59882:'\u6FCB',59883:'\u6FA3',59884:'\u6FC7',59885:'\u6FBC',59886:'\u6FCE',59887:'\u6FC8',59888:'\u6F5E',59889:'\u6FC4',59890:'\u6FBD',59891:'\u6F9E',59892:'\u6FCA',59893:'\u6FA8',59894:'\u7004',59895:'\u6FA5',59896:'\u6FAE',59897:'\u6FBA',59898:'\u6FAC',59899:'\u6FAA',59900:'\u6FCF',59901:'\u6FBF',59902:'\u6FB8',59968:'\u6FA2',59969:'\u6FC9',59970:'\u6FAB',59971:'\u6FCD',59972:'\u6FAF',59973:'\u6FB2',59974:'\u6FB0',59975:'\u71C5',59976:'\u71C2',59977:'\u71BF',59978:'\u71B8',59979:'\u71D6',59980:'\u71C0',59981:'\u71C1',59982:'\u71CB',59983:'\u71D4',59984:'\u71CA',59985:'\u71C7',59986:'\u71CF',59987:'\u71BD',59988:'\u71D8',59989:'\u71BC',59990:'\u71C6',59991:'\u71DA',59992:'\u71DB',59993:'\u729D',59994:'\u729E',59995:'\u7369',59996:'\u7366',59997:'\u7367',59998:'\u736C',59999:'\u7365',60000:'\u736B',60001:'\u736A',60002:'\u747F',60003:'\u749A',60004:'\u74A0',60005:'\u7494',60006:'\u7492',60007:'\u7495',60008:'\u74A1',60009:'\u750B',60010:'\u7580',60011:'\u762F',60012:'\u762D',60013:'\u7631',60014:'\u763D',60015:'\u7633',60016:'\u763C',60017:'\u7635',60018:'\u7632',60019:'\u7630',60020:'\u76BB',60021:'\u76E6',60022:'\u779A',60023:'\u779D',60024:'\u77A1',60025:'\u779C',60026:'\u779B',60027:'\u77A2',60028:'\u77A3',60029:'\u7795',60030:'\u7799',60065:'\u7797',60066:'\u78DD',60067:'\u78E9',60068:'\u78E5',60069:'\u78EA',60070:'\u78DE',60071:'\u78E3',60072:'\u78DB',60073:'\u78E1',60074:'\u78E2',60075:'\u78ED',60076:'\u78DF',60077:'\u78E0',60078:'\u79A4',60079:'\u7A44',60080:'\u7A48',60081:'\u7A47',60082:'\u7AB6',60083:'\u7AB8',60084:'\u7AB5',60085:'\u7AB1',60086:'\u7AB7',60087:'\u7BDE',60088:'\u7BE3',60089:'\u7BE7',60090:'\u7BDD',60091:'\u7BD5',60092:'\u7BE5',60093:'\u7BDA',60094:'\u7BE8',60095:'\u7BF9',60096:'\u7BD4',60097:'\u7BEA',60098:'\u7BE2',60099:'\u7BDC',60100:'\u7BEB',60101:'\u7BD8',60102:'\u7BDF',60103:'\u7CD2',60104:'\u7CD4',60105:'\u7CD7',60106:'\u7CD0',60107:'\u7CD1',60108:'\u7E12',60109:'\u7E21',60110:'\u7E17',60111:'\u7E0C',60112:'\u7E1F',60113:'\u7E20',60114:'\u7E13',60115:'\u7E0E',60116:'\u7E1C',60117:'\u7E15',60118:'\u7E1A',60119:'\u7E22',60120:'\u7E0B',60121:'\u7E0F',60122:'\u7E16',60123:'\u7E0D',60124:'\u7E14',60125:'\u7E25',60126:'\u7E24',60127:'\u7F43',60128:'\u7F7B',60129:'\u7F7C',60130:'\u7F7A',60131:'\u7FB1',60132:'\u7FEF',60133:'\u802A',60134:'\u8029',60135:'\u806C',60136:'\u81B1',60137:'\u81A6',60138:'\u81AE',60139:'\u81B9',60140:'\u81B5',60141:'\u81AB',60142:'\u81B0',60143:'\u81AC',60144:'\u81B4',60145:'\u81B2',60146:'\u81B7',60147:'\u81A7',60148:'\u81F2',60149:'\u8255',60150:'\u8256',60151:'\u8257',60152:'\u8556',60153:'\u8545',60154:'\u856B',60155:'\u854D',60156:'\u8553',60157:'\u8561',60158:'\u8558',60224:'\u8540',60225:'\u8546',60226:'\u8564',60227:'\u8541',60228:'\u8562',60229:'\u8544',60230:'\u8551',60231:'\u8547',60232:'\u8563',60233:'\u853E',60234:'\u855B',60235:'\u8571',60236:'\u854E',60237:'\u856E',60238:'\u8575',60239:'\u8555',60240:'\u8567',60241:'\u8560',60242:'\u858C',60243:'\u8566',60244:'\u855D',60245:'\u8554',60246:'\u8565',60247:'\u856C',60248:'\u8663',60249:'\u8665',60250:'\u8664',60251:'\u879B',60252:'\u878F',60253:'\u8797',60254:'\u8793',60255:'\u8792',60256:'\u8788',60257:'\u8781',60258:'\u8796',60259:'\u8798',60260:'\u8779',60261:'\u8787',60262:'\u87A3',60263:'\u8785',60264:'\u8790',60265:'\u8791',60266:'\u879D',60267:'\u8784',60268:'\u8794',60269:'\u879C',60270:'\u879A',60271:'\u8789',60272:'\u891E',60273:'\u8926',60274:'\u8930',60275:'\u892D',60276:'\u892E',60277:'\u8927',60278:'\u8931',60279:'\u8922',60280:'\u8929',60281:'\u8923',60282:'\u892F',60283:'\u892C',60284:'\u891F',60285:'\u89F1',60286:'\u8AE0',60321:'\u8AE2',60322:'\u8AF2',60323:'\u8AF4',60324:'\u8AF5',60325:'\u8ADD',60326:'\u8B14',60327:'\u8AE4',60328:'\u8ADF',60329:'\u8AF0',60330:'\u8AC8',60331:'\u8ADE',60332:'\u8AE1',60333:'\u8AE8',60334:'\u8AFF',60335:'\u8AEF',60336:'\u8AFB',60337:'\u8C91',60338:'\u8C92',60339:'\u8C90',60340:'\u8CF5',60341:'\u8CEE',60342:'\u8CF1',60343:'\u8CF0',60344:'\u8CF3',60345:'\u8D6C',60346:'\u8D6E',60347:'\u8DA5',60348:'\u8DA7',60349:'\u8E33',60350:'\u8E3E',60351:'\u8E38',60352:'\u8E40',60353:'\u8E45',60354:'\u8E36',60355:'\u8E3C',60356:'\u8E3D',60357:'\u8E41',60358:'\u8E30',60359:'\u8E3F',60360:'\u8EBD',60361:'\u8F36',60362:'\u8F2E',60363:'\u8F35',60364:'\u8F32',60365:'\u8F39',60366:'\u8F37',60367:'\u8F34',60368:'\u9076',60369:'\u9079',60370:'\u907B',60371:'\u9086',60372:'\u90FA',60373:'\u9133',60374:'\u9135',60375:'\u9136',60376:'\u9193',60377:'\u9190',60378:'\u9191',60379:'\u918D',60380:'\u918F',60381:'\u9327',60382:'\u931E',60383:'\u9308',60384:'\u931F',60385:'\u9306',60386:'\u930F',60387:'\u937A',60388:'\u9338',60389:'\u933C',60390:'\u931B',60391:'\u9323',60392:'\u9312',60393:'\u9301',60394:'\u9346',60395:'\u932D',60396:'\u930E',60397:'\u930D',60398:'\u92CB',60399:'\u931D',60400:'\u92FA',60401:'\u9325',60402:'\u9313',60403:'\u92F9',60404:'\u92F7',60405:'\u9334',60406:'\u9302',60407:'\u9324',60408:'\u92FF',60409:'\u9329',60410:'\u9339',60411:'\u9335',60412:'\u932A',60413:'\u9314',60414:'\u930C',60480:'\u930B',60481:'\u92FE',60482:'\u9309',60483:'\u9300',60484:'\u92FB',60485:'\u9316',60486:'\u95BC',60487:'\u95CD',60488:'\u95BE',60489:'\u95B9',60490:'\u95BA',60491:'\u95B6',60492:'\u95BF',60493:'\u95B5',60494:'\u95BD',60495:'\u96A9',60496:'\u96D4',60497:'\u970B',60498:'\u9712',60499:'\u9710',60500:'\u9799',60501:'\u9797',60502:'\u9794',60503:'\u97F0',60504:'\u97F8',60505:'\u9835',60506:'\u982F',60507:'\u9832',60508:'\u9924',60509:'\u991F',60510:'\u9927',60511:'\u9929',60512:'\u999E',60513:'\u99EE',60514:'\u99EC',60515:'\u99E5',60516:'\u99E4',60517:'\u99F0',60518:'\u99E3',60519:'\u99EA',60520:'\u99E9',60521:'\u99E7',60522:'\u9AB9',60523:'\u9ABF',60524:'\u9AB4',60525:'\u9ABB',60526:'\u9AF6',60527:'\u9AFA',60528:'\u9AF9',60529:'\u9AF7',60530:'\u9B33',60531:'\u9B80',60532:'\u9B85',60533:'\u9B87',60534:'\u9B7C',60535:'\u9B7E',60536:'\u9B7B',60537:'\u9B82',60538:'\u9B93',60539:'\u9B92',60540:'\u9B90',60541:'\u9B7A',60542:'\u9B95',60577:'\u9B7D',60578:'\u9B88',60579:'\u9D25',60580:'\u9D17',60581:'\u9D20',60582:'\u9D1E',60583:'\u9D14',60584:'\u9D29',60585:'\u9D1D',60586:'\u9D18',60587:'\u9D22',60588:'\u9D10',60589:'\u9D19',60590:'\u9D1F',60591:'\u9E88',60592:'\u9E86',60593:'\u9E87',60594:'\u9EAE',60595:'\u9EAD',60596:'\u9ED5',60597:'\u9ED6',60598:'\u9EFA',60599:'\u9F12',60600:'\u9F3D',60601:'\u5126',60602:'\u5125',60603:'\u5122',60604:'\u5124',60605:'\u5120',60606:'\u5129',60607:'\u52F4',60608:'\u5693',60609:'\u568C',60610:'\u568D',60611:'\u5686',60612:'\u5684',60613:'\u5683',60614:'\u567E',60615:'\u5682',60616:'\u567F',60617:'\u5681',60618:'\u58D6',60619:'\u58D4',60620:'\u58CF',60621:'\u58D2',60622:'\u5B2D',60623:'\u5B25',60624:'\u5B32',60625:'\u5B23',60626:'\u5B2C',60627:'\u5B27',60628:'\u5B26',60629:'\u5B2F',60630:'\u5B2E',60631:'\u5B7B',60632:'\u5BF1',60633:'\u5BF2',60634:'\u5DB7',60635:'\u5E6C',60636:'\u5E6A',60637:'\u5FBE',60638:'\u5FBB',60639:'\u61C3',60640:'\u61B5',60641:'\u61BC',60642:'\u61E7',60643:'\u61E0',60644:'\u61E5',60645:'\u61E4',60646:'\u61E8',60647:'\u61DE',60648:'\u64EF',60649:'\u64E9',60650:'\u64E3',60651:'\u64EB',60652:'\u64E4',60653:'\u64E8',60654:'\u6581',60655:'\u6580',60656:'\u65B6',60657:'\u65DA',60658:'\u66D2',60659:'\u6A8D',60660:'\u6A96',60661:'\u6A81',60662:'\u6AA5',60663:'\u6A89',60664:'\u6A9F',60665:'\u6A9B',60666:'\u6AA1',60667:'\u6A9E',60668:'\u6A87',60669:'\u6A93',60670:'\u6A8E',60736:'\u6A95',60737:'\u6A83',60738:'\u6AA8',60739:'\u6AA4',60740:'\u6A91',60741:'\u6A7F',60742:'\u6AA6',60743:'\u6A9A',60744:'\u6A85',60745:'\u6A8C',60746:'\u6A92',60747:'\u6B5B',60748:'\u6BAD',60749:'\u6C09',60750:'\u6FCC',60751:'\u6FA9',60752:'\u6FF4',60753:'\u6FD4',60754:'\u6FE3',60755:'\u6FDC',60756:'\u6FED',60757:'\u6FE7',60758:'\u6FE6',60759:'\u6FDE',60760:'\u6FF2',60761:'\u6FDD',60762:'\u6FE2',60763:'\u6FE8',60764:'\u71E1',60765:'\u71F1',60766:'\u71E8',60767:'\u71F2',60768:'\u71E4',60769:'\u71F0',60770:'\u71E2',60771:'\u7373',60772:'\u736E',60773:'\u736F',60774:'\u7497',60775:'\u74B2',60776:'\u74AB',60777:'\u7490',60778:'\u74AA',60779:'\u74AD',60780:'\u74B1',60781:'\u74A5',60782:'\u74AF',60783:'\u7510',60784:'\u7511',60785:'\u7512',60786:'\u750F',60787:'\u7584',60788:'\u7643',60789:'\u7648',60790:'\u7649',60791:'\u7647',60792:'\u76A4',60793:'\u76E9',60794:'\u77B5',60795:'\u77AB',60796:'\u77B2',60797:'\u77B7',60798:'\u77B6',60833:'\u77B4',60834:'\u77B1',60835:'\u77A8',60836:'\u77F0',60837:'\u78F3',60838:'\u78FD',60839:'\u7902',60840:'\u78FB',60841:'\u78FC',60842:'\u78F2',60843:'\u7905',60844:'\u78F9',60845:'\u78FE',60846:'\u7904',60847:'\u79AB',60848:'\u79A8',60849:'\u7A5C',60850:'\u7A5B',60851:'\u7A56',60852:'\u7A58',60853:'\u7A54',60854:'\u7A5A',60855:'\u7ABE',60856:'\u7AC0',60857:'\u7AC1',60858:'\u7C05',60859:'\u7C0F',60860:'\u7BF2',60861:'\u7C00',60862:'\u7BFF',60863:'\u7BFB',60864:'\u7C0E',60865:'\u7BF4',60866:'\u7C0B',60867:'\u7BF3',60868:'\u7C02',60869:'\u7C09',60870:'\u7C03',60871:'\u7C01',60872:'\u7BF8',60873:'\u7BFD',60874:'\u7C06',60875:'\u7BF0',60876:'\u7BF1',60877:'\u7C10',60878:'\u7C0A',60879:'\u7CE8',60880:'\u7E2D',60881:'\u7E3C',60882:'\u7E42',60883:'\u7E33',60884:'\u9848',60885:'\u7E38',60886:'\u7E2A',60887:'\u7E49',60888:'\u7E40',60889:'\u7E47',60890:'\u7E29',60891:'\u7E4C',60892:'\u7E30',60893:'\u7E3B',60894:'\u7E36',60895:'\u7E44',60896:'\u7E3A',60897:'\u7F45',60898:'\u7F7F',60899:'\u7F7E',60900:'\u7F7D',60901:'\u7FF4',60902:'\u7FF2',60903:'\u802C',60904:'\u81BB',60905:'\u81C4',60906:'\u81CC',60907:'\u81CA',60908:'\u81C5',60909:'\u81C7',60910:'\u81BC',60911:'\u81E9',60912:'\u825B',60913:'\u825A',60914:'\u825C',60915:'\u8583',60916:'\u8580',60917:'\u858F',60918:'\u85A7',60919:'\u8595',60920:'\u85A0',60921:'\u858B',60922:'\u85A3',60923:'\u857B',60924:'\u85A4',60925:'\u859A',60926:'\u859E',60992:'\u8577',60993:'\u857C',60994:'\u8589',60995:'\u85A1',60996:'\u857A',60997:'\u8578',60998:'\u8557',60999:'\u858E',61000:'\u8596',61001:'\u8586',61002:'\u858D',61003:'\u8599',61004:'\u859D',61005:'\u8581',61006:'\u85A2',61007:'\u8582',61008:'\u8588',61009:'\u8585',61010:'\u8579',61011:'\u8576',61012:'\u8598',61013:'\u8590',61014:'\u859F',61015:'\u8668',61016:'\u87BE',61017:'\u87AA',61018:'\u87AD',61019:'\u87C5',61020:'\u87B0',61021:'\u87AC',61022:'\u87B9',61023:'\u87B5',61024:'\u87BC',61025:'\u87AE',61026:'\u87C9',61027:'\u87C3',61028:'\u87C2',61029:'\u87CC',61030:'\u87B7',61031:'\u87AF',61032:'\u87C4',61033:'\u87CA',61034:'\u87B4',61035:'\u87B6',61036:'\u87BF',61037:'\u87B8',61038:'\u87BD',61039:'\u87DE',61040:'\u87B2',61041:'\u8935',61042:'\u8933',61043:'\u893C',61044:'\u893E',61045:'\u8941',61046:'\u8952',61047:'\u8937',61048:'\u8942',61049:'\u89AD',61050:'\u89AF',61051:'\u89AE',61052:'\u89F2',61053:'\u89F3',61054:'\u8B1E',61089:'\u8B18',61090:'\u8B16',61091:'\u8B11',61092:'\u8B05',61093:'\u8B0B',61094:'\u8B22',61095:'\u8B0F',61096:'\u8B12',61097:'\u8B15',61098:'\u8B07',61099:'\u8B0D',61100:'\u8B08',61101:'\u8B06',61102:'\u8B1C',61103:'\u8B13',61104:'\u8B1A',61105:'\u8C4F',61106:'\u8C70',61107:'\u8C72',61108:'\u8C71',61109:'\u8C6F',61110:'\u8C95',61111:'\u8C94',61112:'\u8CF9',61113:'\u8D6F',61114:'\u8E4E',61115:'\u8E4D',61116:'\u8E53',61117:'\u8E50',61118:'\u8E4C',61119:'\u8E47',61120:'\u8F43',61121:'\u8F40',61122:'\u9085',61123:'\u907E',61124:'\u9138',61125:'\u919A',61126:'\u91A2',61127:'\u919B',61128:'\u9199',61129:'\u919F',61130:'\u91A1',61131:'\u919D',61132:'\u91A0',61133:'\u93A1',61134:'\u9383',61135:'\u93AF',61136:'\u9364',61137:'\u9356',61138:'\u9347',61139:'\u937C',61140:'\u9358',61141:'\u935C',61142:'\u9376',61143:'\u9349',61144:'\u9350',61145:'\u9351',61146:'\u9360',61147:'\u936D',61148:'\u938F',61149:'\u934C',61150:'\u936A',61151:'\u9379',61152:'\u9357',61153:'\u9355',61154:'\u9352',61155:'\u934F',61156:'\u9371',61157:'\u9377',61158:'\u937B',61159:'\u9361',61160:'\u935E',61161:'\u9363',61162:'\u9367',61163:'\u9380',61164:'\u934E',61165:'\u9359',61166:'\u95C7',61167:'\u95C0',61168:'\u95C9',61169:'\u95C3',61170:'\u95C5',61171:'\u95B7',61172:'\u96AE',61173:'\u96B0',61174:'\u96AC',61175:'\u9720',61176:'\u971F',61177:'\u9718',61178:'\u971D',61179:'\u9719',61180:'\u979A',61181:'\u97A1',61182:'\u979C',61248:'\u979E',61249:'\u979D',61250:'\u97D5',61251:'\u97D4',61252:'\u97F1',61253:'\u9841',61254:'\u9844',61255:'\u984A',61256:'\u9849',61257:'\u9845',61258:'\u9843',61259:'\u9925',61260:'\u992B',61261:'\u992C',61262:'\u992A',61263:'\u9933',61264:'\u9932',61265:'\u992F',61266:'\u992D',61267:'\u9931',61268:'\u9930',61269:'\u9998',61270:'\u99A3',61271:'\u99A1',61272:'\u9A02',61273:'\u99FA',61274:'\u99F4',61275:'\u99F7',61276:'\u99F9',61277:'\u99F8',61278:'\u99F6',61279:'\u99FB',61280:'\u99FD',61281:'\u99FE',61282:'\u99FC',61283:'\u9A03',61284:'\u9ABE',61285:'\u9AFE',61286:'\u9AFD',61287:'\u9B01',61288:'\u9AFC',61289:'\u9B48',61290:'\u9B9A',61291:'\u9BA8',61292:'\u9B9E',61293:'\u9B9B',61294:'\u9BA6',61295:'\u9BA1',61296:'\u9BA5',61297:'\u9BA4',61298:'\u9B86',61299:'\u9BA2',61300:'\u9BA0',61301:'\u9BAF',61302:'\u9D33',61303:'\u9D41',61304:'\u9D67',61305:'\u9D36',61306:'\u9D2E',61307:'\u9D2F',61308:'\u9D31',61309:'\u9D38',61310:'\u9D30',61345:'\u9D45',61346:'\u9D42',61347:'\u9D43',61348:'\u9D3E',61349:'\u9D37',61350:'\u9D40',61351:'\u9D3D',61352:'\u7FF5',61353:'\u9D2D',61354:'\u9E8A',61355:'\u9E89',61356:'\u9E8D',61357:'\u9EB0',61358:'\u9EC8',61359:'\u9EDA',61360:'\u9EFB',61361:'\u9EFF',61362:'\u9F24',61363:'\u9F23',61364:'\u9F22',61365:'\u9F54',61366:'\u9FA0',61367:'\u5131',61368:'\u512D',61369:'\u512E',61370:'\u5698',61371:'\u569C',61372:'\u5697',61373:'\u569A',61374:'\u569D',61375:'\u5699',61376:'\u5970',61377:'\u5B3C',61378:'\u5C69',61379:'\u5C6A',61380:'\u5DC0',61381:'\u5E6D',61382:'\u5E6E',61383:'\u61D8',61384:'\u61DF',61385:'\u61ED',61386:'\u61EE',61387:'\u61F1',61388:'\u61EA',61389:'\u61F0',61390:'\u61EB',61391:'\u61D6',61392:'\u61E9',61393:'\u64FF',61394:'\u6504',61395:'\u64FD',61396:'\u64F8',61397:'\u6501',61398:'\u6503',61399:'\u64FC',61400:'\u6594',61401:'\u65DB',61402:'\u66DA',61403:'\u66DB',61404:'\u66D8',61405:'\u6AC5',61406:'\u6AB9',61407:'\u6ABD',61408:'\u6AE1',61409:'\u6AC6',61410:'\u6ABA',61411:'\u6AB6',61412:'\u6AB7',61413:'\u6AC7',61414:'\u6AB4',61415:'\u6AAD',61416:'\u6B5E',61417:'\u6BC9',61418:'\u6C0B',61419:'\u7007',61420:'\u700C',61421:'\u700D',61422:'\u7001',61423:'\u7005',61424:'\u7014',61425:'\u700E',61426:'\u6FFF',61427:'\u7000',61428:'\u6FFB',61429:'\u7026',61430:'\u6FFC',61431:'\u6FF7',61432:'\u700A',61433:'\u7201',61434:'\u71FF',61435:'\u71F9',61436:'\u7203',61437:'\u71FD',61438:'\u7376',61504:'\u74B8',61505:'\u74C0',61506:'\u74B5',61507:'\u74C1',61508:'\u74BE',61509:'\u74B6',61510:'\u74BB',61511:'\u74C2',61512:'\u7514',61513:'\u7513',61514:'\u765C',61515:'\u7664',61516:'\u7659',61517:'\u7650',61518:'\u7653',61519:'\u7657',61520:'\u765A',61521:'\u76A6',61522:'\u76BD',61523:'\u76EC',61524:'\u77C2',61525:'\u77BA',61526:'\u78FF',61527:'\u790C',61528:'\u7913',61529:'\u7914',61530:'\u7909',61531:'\u7910',61532:'\u7912',61533:'\u7911',61534:'\u79AD',61535:'\u79AC',61536:'\u7A5F',61537:'\u7C1C',61538:'\u7C29',61539:'\u7C19',61540:'\u7C20',61541:'\u7C1F',61542:'\u7C2D',61543:'\u7C1D',61544:'\u7C26',61545:'\u7C28',61546:'\u7C22',61547:'\u7C25',61548:'\u7C30',61549:'\u7E5C',61550:'\u7E50',61551:'\u7E56',61552:'\u7E63',61553:'\u7E58',61554:'\u7E62',61555:'\u7E5F',61556:'\u7E51',61557:'\u7E60',61558:'\u7E57',61559:'\u7E53',61560:'\u7FB5',61561:'\u7FB3',61562:'\u7FF7',61563:'\u7FF8',61564:'\u8075',61565:'\u81D1',61566:'\u81D2',61601:'\u81D0',61602:'\u825F',61603:'\u825E',61604:'\u85B4',61605:'\u85C6',61606:'\u85C0',61607:'\u85C3',61608:'\u85C2',61609:'\u85B3',61610:'\u85B5',61611:'\u85BD',61612:'\u85C7',61613:'\u85C4',61614:'\u85BF',61615:'\u85CB',61616:'\u85CE',61617:'\u85C8',61618:'\u85C5',61619:'\u85B1',61620:'\u85B6',61621:'\u85D2',61622:'\u8624',61623:'\u85B8',61624:'\u85B7',61625:'\u85BE',61626:'\u8669',61627:'\u87E7',61628:'\u87E6',61629:'\u87E2',61630:'\u87DB',61631:'\u87EB',61632:'\u87EA',61633:'\u87E5',61634:'\u87DF',61635:'\u87F3',61636:'\u87E4',61637:'\u87D4',61638:'\u87DC',61639:'\u87D3',61640:'\u87ED',61641:'\u87D8',61642:'\u87E3',61643:'\u87A4',61644:'\u87D7',61645:'\u87D9',61646:'\u8801',61647:'\u87F4',61648:'\u87E8',61649:'\u87DD',61650:'\u8953',61651:'\u894B',61652:'\u894F',61653:'\u894C',61654:'\u8946',61655:'\u8950',61656:'\u8951',61657:'\u8949',61658:'\u8B2A',61659:'\u8B27',61660:'\u8B23',61661:'\u8B33',61662:'\u8B30',61663:'\u8B35',61664:'\u8B47',61665:'\u8B2F',61666:'\u8B3C',61667:'\u8B3E',61668:'\u8B31',61669:'\u8B25',61670:'\u8B37',61671:'\u8B26',61672:'\u8B36',61673:'\u8B2E',61674:'\u8B24',61675:'\u8B3B',61676:'\u8B3D',61677:'\u8B3A',61678:'\u8C42',61679:'\u8C75',61680:'\u8C99',61681:'\u8C98',61682:'\u8C97',61683:'\u8CFE',61684:'\u8D04',61685:'\u8D02',61686:'\u8D00',61687:'\u8E5C',61688:'\u8E62',61689:'\u8E60',61690:'\u8E57',61691:'\u8E56',61692:'\u8E5E',61693:'\u8E65',61694:'\u8E67',61760:'\u8E5B',61761:'\u8E5A',61762:'\u8E61',61763:'\u8E5D',61764:'\u8E69',61765:'\u8E54',61766:'\u8F46',61767:'\u8F47',61768:'\u8F48',61769:'\u8F4B',61770:'\u9128',61771:'\u913A',61772:'\u913B',61773:'\u913E',61774:'\u91A8',61775:'\u91A5',61776:'\u91A7',61777:'\u91AF',61778:'\u91AA',61779:'\u93B5',61780:'\u938C',61781:'\u9392',61782:'\u93B7',61783:'\u939B',61784:'\u939D',61785:'\u9389',61786:'\u93A7',61787:'\u938E',61788:'\u93AA',61789:'\u939E',61790:'\u93A6',61791:'\u9395',61792:'\u9388',61793:'\u9399',61794:'\u939F',61795:'\u938D',61796:'\u93B1',61797:'\u9391',61798:'\u93B2',61799:'\u93A4',61800:'\u93A8',61801:'\u93B4',61802:'\u93A3',61803:'\u93A5',61804:'\u95D2',61805:'\u95D3',61806:'\u95D1',61807:'\u96B3',61808:'\u96D7',61809:'\u96DA',61810:'\u5DC2',61811:'\u96DF',61812:'\u96D8',61813:'\u96DD',61814:'\u9723',61815:'\u9722',61816:'\u9725',61817:'\u97AC',61818:'\u97AE',61819:'\u97A8',61820:'\u97AB',61821:'\u97A4',61822:'\u97AA',61857:'\u97A2',61858:'\u97A5',61859:'\u97D7',61860:'\u97D9',61861:'\u97D6',61862:'\u97D8',61863:'\u97FA',61864:'\u9850',61865:'\u9851',61866:'\u9852',61867:'\u98B8',61868:'\u9941',61869:'\u993C',61870:'\u993A',61871:'\u9A0F',61872:'\u9A0B',61873:'\u9A09',61874:'\u9A0D',61875:'\u9A04',61876:'\u9A11',61877:'\u9A0A',61878:'\u9A05',61879:'\u9A07',61880:'\u9A06',61881:'\u9AC0',61882:'\u9ADC',61883:'\u9B08',61884:'\u9B04',61885:'\u9B05',61886:'\u9B29',61887:'\u9B35',61888:'\u9B4A',61889:'\u9B4C',61890:'\u9B4B',61891:'\u9BC7',61892:'\u9BC6',61893:'\u9BC3',61894:'\u9BBF',61895:'\u9BC1',61896:'\u9BB5',61897:'\u9BB8',61898:'\u9BD3',61899:'\u9BB6',61900:'\u9BC4',61901:'\u9BB9',61902:'\u9BBD',61903:'\u9D5C',61904:'\u9D53',61905:'\u9D4F',61906:'\u9D4A',61907:'\u9D5B',61908:'\u9D4B',61909:'\u9D59',61910:'\u9D56',61911:'\u9D4C',61912:'\u9D57',61913:'\u9D52',61914:'\u9D54',61915:'\u9D5F',61916:'\u9D58',61917:'\u9D5A',61918:'\u9E8E',61919:'\u9E8C',61920:'\u9EDF',61921:'\u9F01',61922:'\u9F00',61923:'\u9F16',61924:'\u9F25',61925:'\u9F2B',61926:'\u9F2A',61927:'\u9F29',61928:'\u9F28',61929:'\u9F4C',61930:'\u9F55',61931:'\u5134',61932:'\u5135',61933:'\u5296',61934:'\u52F7',61935:'\u53B4',61936:'\u56AB',61937:'\u56AD',61938:'\u56A6',61939:'\u56A7',61940:'\u56AA',61941:'\u56AC',61942:'\u58DA',61943:'\u58DD',61944:'\u58DB',61945:'\u5912',61946:'\u5B3D',61947:'\u5B3E',61948:'\u5B3F',61949:'\u5DC3',61950:'\u5E70',62016:'\u5FBF',62017:'\u61FB',62018:'\u6507',62019:'\u6510',62020:'\u650D',62021:'\u6509',62022:'\u650C',62023:'\u650E',62024:'\u6584',62025:'\u65DE',62026:'\u65DD',62027:'\u66DE',62028:'\u6AE7',62029:'\u6AE0',62030:'\u6ACC',62031:'\u6AD1',62032:'\u6AD9',62033:'\u6ACB',62034:'\u6ADF',62035:'\u6ADC',62036:'\u6AD0',62037:'\u6AEB',62038:'\u6ACF',62039:'\u6ACD',62040:'\u6ADE',62041:'\u6B60',62042:'\u6BB0',62043:'\u6C0C',62044:'\u7019',62045:'\u7027',62046:'\u7020',62047:'\u7016',62048:'\u702B',62049:'\u7021',62050:'\u7022',62051:'\u7023',62052:'\u7029',62053:'\u7017',62054:'\u7024',62055:'\u701C',62056:'\u702A',62057:'\u720C',62058:'\u720A',62059:'\u7207',62060:'\u7202',62061:'\u7205',62062:'\u72A5',62063:'\u72A6',62064:'\u72A4',62065:'\u72A3',62066:'\u72A1',62067:'\u74CB',62068:'\u74C5',62069:'\u74B7',62070:'\u74C3',62071:'\u7516',62072:'\u7660',62073:'\u77C9',62074:'\u77CA',62075:'\u77C4',62076:'\u77F1',62077:'\u791D',62078:'\u791B',62113:'\u7921',62114:'\u791C',62115:'\u7917',62116:'\u791E',62117:'\u79B0',62118:'\u7A67',62119:'\u7A68',62120:'\u7C33',62121:'\u7C3C',62122:'\u7C39',62123:'\u7C2C',62124:'\u7C3B',62125:'\u7CEC',62126:'\u7CEA',62127:'\u7E76',62128:'\u7E75',62129:'\u7E78',62130:'\u7E70',62131:'\u7E77',62132:'\u7E6F',62133:'\u7E7A',62134:'\u7E72',62135:'\u7E74',62136:'\u7E68',62137:'\u7F4B',62138:'\u7F4A',62139:'\u7F83',62140:'\u7F86',62141:'\u7FB7',62142:'\u7FFD',62143:'\u7FFE',62144:'\u8078',62145:'\u81D7',62146:'\u81D5',62147:'\u8264',62148:'\u8261',62149:'\u8263',62150:'\u85EB',62151:'\u85F1',62152:'\u85ED',62153:'\u85D9',62154:'\u85E1',62155:'\u85E8',62156:'\u85DA',62157:'\u85D7',62158:'\u85EC',62159:'\u85F2',62160:'\u85F8',62161:'\u85D8',62162:'\u85DF',62163:'\u85E3',62164:'\u85DC',62165:'\u85D1',62166:'\u85F0',62167:'\u85E6',62168:'\u85EF',62169:'\u85DE',62170:'\u85E2',62171:'\u8800',62172:'\u87FA',62173:'\u8803',62174:'\u87F6',62175:'\u87F7',62176:'\u8809',62177:'\u880C',62178:'\u880B',62179:'\u8806',62180:'\u87FC',62181:'\u8808',62182:'\u87FF',62183:'\u880A',62184:'\u8802',62185:'\u8962',62186:'\u895A',62187:'\u895B',62188:'\u8957',62189:'\u8961',62190:'\u895C',62191:'\u8958',62192:'\u895D',62193:'\u8959',62194:'\u8988',62195:'\u89B7',62196:'\u89B6',62197:'\u89F6',62198:'\u8B50',62199:'\u8B48',62200:'\u8B4A',62201:'\u8B40',62202:'\u8B53',62203:'\u8B56',62204:'\u8B54',62205:'\u8B4B',62206:'\u8B55',62272:'\u8B51',62273:'\u8B42',62274:'\u8B52',62275:'\u8B57',62276:'\u8C43',62277:'\u8C77',62278:'\u8C76',62279:'\u8C9A',62280:'\u8D06',62281:'\u8D07',62282:'\u8D09',62283:'\u8DAC',62284:'\u8DAA',62285:'\u8DAD',62286:'\u8DAB',62287:'\u8E6D',62288:'\u8E78',62289:'\u8E73',62290:'\u8E6A',62291:'\u8E6F',62292:'\u8E7B',62293:'\u8EC2',62294:'\u8F52',62295:'\u8F51',62296:'\u8F4F',62297:'\u8F50',62298:'\u8F53',62299:'\u8FB4',62300:'\u9140',62301:'\u913F',62302:'\u91B0',62303:'\u91AD',62304:'\u93DE',62305:'\u93C7',62306:'\u93CF',62307:'\u93C2',62308:'\u93DA',62309:'\u93D0',62310:'\u93F9',62311:'\u93EC',62312:'\u93CC',62313:'\u93D9',62314:'\u93A9',62315:'\u93E6',62316:'\u93CA',62317:'\u93D4',62318:'\u93EE',62319:'\u93E3',62320:'\u93D5',62321:'\u93C4',62322:'\u93CE',62323:'\u93C0',62324:'\u93D2',62325:'\u93E7',62326:'\u957D',62327:'\u95DA',62328:'\u95DB',62329:'\u96E1',62330:'\u9729',62331:'\u972B',62332:'\u972C',62333:'\u9728',62334:'\u9726',62369:'\u97B3',62370:'\u97B7',62371:'\u97B6',62372:'\u97DD',62373:'\u97DE',62374:'\u97DF',62375:'\u985C',62376:'\u9859',62377:'\u985D',62378:'\u9857',62379:'\u98BF',62380:'\u98BD',62381:'\u98BB',62382:'\u98BE',62383:'\u9948',62384:'\u9947',62385:'\u9943',62386:'\u99A6',62387:'\u99A7',62388:'\u9A1A',62389:'\u9A15',62390:'\u9A25',62391:'\u9A1D',62392:'\u9A24',62393:'\u9A1B',62394:'\u9A22',62395:'\u9A20',62396:'\u9A27',62397:'\u9A23',62398:'\u9A1E',62399:'\u9A1C',62400:'\u9A14',62401:'\u9AC2',62402:'\u9B0B',62403:'\u9B0A',62404:'\u9B0E',62405:'\u9B0C',62406:'\u9B37',62407:'\u9BEA',62408:'\u9BEB',62409:'\u9BE0',62410:'\u9BDE',62411:'\u9BE4',62412:'\u9BE6',62413:'\u9BE2',62414:'\u9BF0',62415:'\u9BD4',62416:'\u9BD7',62417:'\u9BEC',62418:'\u9BDC',62419:'\u9BD9',62420:'\u9BE5',62421:'\u9BD5',62422:'\u9BE1',62423:'\u9BDA',62424:'\u9D77',62425:'\u9D81',62426:'\u9D8A',62427:'\u9D84',62428:'\u9D88',62429:'\u9D71',62430:'\u9D80',62431:'\u9D78',62432:'\u9D86',62433:'\u9D8B',62434:'\u9D8C',62435:'\u9D7D',62436:'\u9D6B',62437:'\u9D74',62438:'\u9D75',62439:'\u9D70',62440:'\u9D69',62441:'\u9D85',62442:'\u9D73',62443:'\u9D7B',62444:'\u9D82',62445:'\u9D6F',62446:'\u9D79',62447:'\u9D7F',62448:'\u9D87',62449:'\u9D68',62450:'\u9E94',62451:'\u9E91',62452:'\u9EC0',62453:'\u9EFC',62454:'\u9F2D',62455:'\u9F40',62456:'\u9F41',62457:'\u9F4D',62458:'\u9F56',62459:'\u9F57',62460:'\u9F58',62461:'\u5337',62462:'\u56B2',62528:'\u56B5',62529:'\u56B3',62530:'\u58E3',62531:'\u5B45',62532:'\u5DC6',62533:'\u5DC7',62534:'\u5EEE',62535:'\u5EEF',62536:'\u5FC0',62537:'\u5FC1',62538:'\u61F9',62539:'\u6517',62540:'\u6516',62541:'\u6515',62542:'\u6513',62543:'\u65DF',62544:'\u66E8',62545:'\u66E3',62546:'\u66E4',62547:'\u6AF3',62548:'\u6AF0',62549:'\u6AEA',62550:'\u6AE8',62551:'\u6AF9',62552:'\u6AF1',62553:'\u6AEE',62554:'\u6AEF',62555:'\u703C',62556:'\u7035',62557:'\u702F',62558:'\u7037',62559:'\u7034',62560:'\u7031',62561:'\u7042',62562:'\u7038',62563:'\u703F',62564:'\u703A',62565:'\u7039',62566:'\u7040',62567:'\u703B',62568:'\u7033',62569:'\u7041',62570:'\u7213',62571:'\u7214',62572:'\u72A8',62573:'\u737D',62574:'\u737C',62575:'\u74BA',62576:'\u76AB',62577:'\u76AA',62578:'\u76BE',62579:'\u76ED',62580:'\u77CC',62581:'\u77CE',62582:'\u77CF',62583:'\u77CD',62584:'\u77F2',62585:'\u7925',62586:'\u7923',62587:'\u7927',62588:'\u7928',62589:'\u7924',62590:'\u7929',62625:'\u79B2',62626:'\u7A6E',62627:'\u7A6C',62628:'\u7A6D',62629:'\u7AF7',62630:'\u7C49',62631:'\u7C48',62632:'\u7C4A',62633:'\u7C47',62634:'\u7C45',62635:'\u7CEE',62636:'\u7E7B',62637:'\u7E7E',62638:'\u7E81',62639:'\u7E80',62640:'\u7FBA',62641:'\u7FFF',62642:'\u8079',62643:'\u81DB',62644:'\u81D9',62645:'\u820B',62646:'\u8268',62647:'\u8269',62648:'\u8622',62649:'\u85FF',62650:'\u8601',62651:'\u85FE',62652:'\u861B',62653:'\u8600',62654:'\u85F6',62655:'\u8604',62656:'\u8609',62657:'\u8605',62658:'\u860C',62659:'\u85FD',62660:'\u8819',62661:'\u8810',62662:'\u8811',62663:'\u8817',62664:'\u8813',62665:'\u8816',62666:'\u8963',62667:'\u8966',62668:'\u89B9',62669:'\u89F7',62670:'\u8B60',62671:'\u8B6A',62672:'\u8B5D',62673:'\u8B68',62674:'\u8B63',62675:'\u8B65',62676:'\u8B67',62677:'\u8B6D',62678:'\u8DAE',62679:'\u8E86',62680:'\u8E88',62681:'\u8E84',62682:'\u8F59',62683:'\u8F56',62684:'\u8F57',62685:'\u8F55',62686:'\u8F58',62687:'\u8F5A',62688:'\u908D',62689:'\u9143',62690:'\u9141',62691:'\u91B7',62692:'\u91B5',62693:'\u91B2',62694:'\u91B3',62695:'\u940B',62696:'\u9413',62697:'\u93FB',62698:'\u9420',62699:'\u940F',62700:'\u9414',62701:'\u93FE',62702:'\u9415',62703:'\u9410',62704:'\u9428',62705:'\u9419',62706:'\u940D',62707:'\u93F5',62708:'\u9400',62709:'\u93F7',62710:'\u9407',62711:'\u940E',62712:'\u9416',62713:'\u9412',62714:'\u93FA',62715:'\u9409',62716:'\u93F8',62717:'\u940A',62718:'\u93FF',62784:'\u93FC',62785:'\u940C',62786:'\u93F6',62787:'\u9411',62788:'\u9406',62789:'\u95DE',62790:'\u95E0',62791:'\u95DF',62792:'\u972E',62793:'\u972F',62794:'\u97B9',62795:'\u97BB',62796:'\u97FD',62797:'\u97FE',62798:'\u9860',62799:'\u9862',62800:'\u9863',62801:'\u985F',62802:'\u98C1',62803:'\u98C2',62804:'\u9950',62805:'\u994E',62806:'\u9959',62807:'\u994C',62808:'\u994B',62809:'\u9953',62810:'\u9A32',62811:'\u9A34',62812:'\u9A31',62813:'\u9A2C',62814:'\u9A2A',62815:'\u9A36',62816:'\u9A29',62817:'\u9A2E',62818:'\u9A38',62819:'\u9A2D',62820:'\u9AC7',62821:'\u9ACA',62822:'\u9AC6',62823:'\u9B10',62824:'\u9B12',62825:'\u9B11',62826:'\u9C0B',62827:'\u9C08',62828:'\u9BF7',62829:'\u9C05',62830:'\u9C12',62831:'\u9BF8',62832:'\u9C40',62833:'\u9C07',62834:'\u9C0E',62835:'\u9C06',62836:'\u9C17',62837:'\u9C14',62838:'\u9C09',62839:'\u9D9F',62840:'\u9D99',62841:'\u9DA4',62842:'\u9D9D',62843:'\u9D92',62844:'\u9D98',62845:'\u9D90',62846:'\u9D9B',62881:'\u9DA0',62882:'\u9D94',62883:'\u9D9C',62884:'\u9DAA',62885:'\u9D97',62886:'\u9DA1',62887:'\u9D9A',62888:'\u9DA2',62889:'\u9DA8',62890:'\u9D9E',62891:'\u9DA3',62892:'\u9DBF',62893:'\u9DA9',62894:'\u9D96',62895:'\u9DA6',62896:'\u9DA7',62897:'\u9E99',62898:'\u9E9B',62899:'\u9E9A',62900:'\u9EE5',62901:'\u9EE4',62902:'\u9EE7',62903:'\u9EE6',62904:'\u9F30',62905:'\u9F2E',62906:'\u9F5B',62907:'\u9F60',62908:'\u9F5E',62909:'\u9F5D',62910:'\u9F59',62911:'\u9F91',62912:'\u513A',62913:'\u5139',62914:'\u5298',62915:'\u5297',62916:'\u56C3',62917:'\u56BD',62918:'\u56BE',62919:'\u5B48',62920:'\u5B47',62921:'\u5DCB',62922:'\u5DCF',62923:'\u5EF1',62924:'\u61FD',62925:'\u651B',62926:'\u6B02',62927:'\u6AFC',62928:'\u6B03',62929:'\u6AF8',62930:'\u6B00',62931:'\u7043',62932:'\u7044',62933:'\u704A',62934:'\u7048',62935:'\u7049',62936:'\u7045',62937:'\u7046',62938:'\u721D',62939:'\u721A',62940:'\u7219',62941:'\u737E',62942:'\u7517',62943:'\u766A',62944:'\u77D0',62945:'\u792D',62946:'\u7931',62947:'\u792F',62948:'\u7C54',62949:'\u7C53',62950:'\u7CF2',62951:'\u7E8A',62952:'\u7E87',62953:'\u7E88',62954:'\u7E8B',62955:'\u7E86',62956:'\u7E8D',62957:'\u7F4D',62958:'\u7FBB',62959:'\u8030',62960:'\u81DD',62961:'\u8618',62962:'\u862A',62963:'\u8626',62964:'\u861F',62965:'\u8623',62966:'\u861C',62967:'\u8619',62968:'\u8627',62969:'\u862E',62970:'\u8621',62971:'\u8620',62972:'\u8629',62973:'\u861E',62974:'\u8625',63040:'\u8829',63041:'\u881D',63042:'\u881B',63043:'\u8820',63044:'\u8824',63045:'\u881C',63046:'\u882B',63047:'\u884A',63048:'\u896D',63049:'\u8969',63050:'\u896E',63051:'\u896B',63052:'\u89FA',63053:'\u8B79',63054:'\u8B78',63055:'\u8B45',63056:'\u8B7A',63057:'\u8B7B',63058:'\u8D10',63059:'\u8D14',63060:'\u8DAF',63061:'\u8E8E',63062:'\u8E8C',63063:'\u8F5E',63064:'\u8F5B',63065:'\u8F5D',63066:'\u9146',63067:'\u9144',63068:'\u9145',63069:'\u91B9',63070:'\u943F',63071:'\u943B',63072:'\u9436',63073:'\u9429',63074:'\u943D',63075:'\u943C',63076:'\u9430',63077:'\u9439',63078:'\u942A',63079:'\u9437',63080:'\u942C',63081:'\u9440',63082:'\u9431',63083:'\u95E5',63084:'\u95E4',63085:'\u95E3',63086:'\u9735',63087:'\u973A',63088:'\u97BF',63089:'\u97E1',63090:'\u9864',63091:'\u98C9',63092:'\u98C6',63093:'\u98C0',63094:'\u9958',63095:'\u9956',63096:'\u9A39',63097:'\u9A3D',63098:'\u9A46',63099:'\u9A44',63100:'\u9A42',63101:'\u9A41',63102:'\u9A3A',63137:'\u9A3F',63138:'\u9ACD',63139:'\u9B15',63140:'\u9B17',63141:'\u9B18',63142:'\u9B16',63143:'\u9B3A',63144:'\u9B52',63145:'\u9C2B',63146:'\u9C1D',63147:'\u9C1C',63148:'\u9C2C',63149:'\u9C23',63150:'\u9C28',63151:'\u9C29',63152:'\u9C24',63153:'\u9C21',63154:'\u9DB7',63155:'\u9DB6',63156:'\u9DBC',63157:'\u9DC1',63158:'\u9DC7',63159:'\u9DCA',63160:'\u9DCF',63161:'\u9DBE',63162:'\u9DC5',63163:'\u9DC3',63164:'\u9DBB',63165:'\u9DB5',63166:'\u9DCE',63167:'\u9DB9',63168:'\u9DBA',63169:'\u9DAC',63170:'\u9DC8',63171:'\u9DB1',63172:'\u9DAD',63173:'\u9DCC',63174:'\u9DB3',63175:'\u9DCD',63176:'\u9DB2',63177:'\u9E7A',63178:'\u9E9C',63179:'\u9EEB',63180:'\u9EEE',63181:'\u9EED',63182:'\u9F1B',63183:'\u9F18',63184:'\u9F1A',63185:'\u9F31',63186:'\u9F4E',63187:'\u9F65',63188:'\u9F64',63189:'\u9F92',63190:'\u4EB9',63191:'\u56C6',63192:'\u56C5',63193:'\u56CB',63194:'\u5971',63195:'\u5B4B',63196:'\u5B4C',63197:'\u5DD5',63198:'\u5DD1',63199:'\u5EF2',63200:'\u6521',63201:'\u6520',63202:'\u6526',63203:'\u6522',63204:'\u6B0B',63205:'\u6B08',63206:'\u6B09',63207:'\u6C0D',63208:'\u7055',63209:'\u7056',63210:'\u7057',63211:'\u7052',63212:'\u721E',63213:'\u721F',63214:'\u72A9',63215:'\u737F',63216:'\u74D8',63217:'\u74D5',63218:'\u74D9',63219:'\u74D7',63220:'\u766D',63221:'\u76AD',63222:'\u7935',63223:'\u79B4',63224:'\u7A70',63225:'\u7A71',63226:'\u7C57',63227:'\u7C5C',63228:'\u7C59',63229:'\u7C5B',63230:'\u7C5A',63296:'\u7CF4',63297:'\u7CF1',63298:'\u7E91',63299:'\u7F4F',63300:'\u7F87',63301:'\u81DE',63302:'\u826B',63303:'\u8634',63304:'\u8635',63305:'\u8633',63306:'\u862C',63307:'\u8632',63308:'\u8636',63309:'\u882C',63310:'\u8828',63311:'\u8826',63312:'\u882A',63313:'\u8825',63314:'\u8971',63315:'\u89BF',63316:'\u89BE',63317:'\u89FB',63318:'\u8B7E',63319:'\u8B84',63320:'\u8B82',63321:'\u8B86',63322:'\u8B85',63323:'\u8B7F',63324:'\u8D15',63325:'\u8E95',63326:'\u8E94',63327:'\u8E9A',63328:'\u8E92',63329:'\u8E90',63330:'\u8E96',63331:'\u8E97',63332:'\u8F60',63333:'\u8F62',63334:'\u9147',63335:'\u944C',63336:'\u9450',63337:'\u944A',63338:'\u944B',63339:'\u944F',63340:'\u9447',63341:'\u9445',63342:'\u9448',63343:'\u9449',63344:'\u9446',63345:'\u973F',63346:'\u97E3',63347:'\u986A',63348:'\u9869',63349:'\u98CB',63350:'\u9954',63351:'\u995B',63352:'\u9A4E',63353:'\u9A53',63354:'\u9A54',63355:'\u9A4C',63356:'\u9A4F',63357:'\u9A48',63358:'\u9A4A',63393:'\u9A49',63394:'\u9A52',63395:'\u9A50',63396:'\u9AD0',63397:'\u9B19',63398:'\u9B2B',63399:'\u9B3B',63400:'\u9B56',63401:'\u9B55',63402:'\u9C46',63403:'\u9C48',63404:'\u9C3F',63405:'\u9C44',63406:'\u9C39',63407:'\u9C33',63408:'\u9C41',63409:'\u9C3C',63410:'\u9C37',63411:'\u9C34',63412:'\u9C32',63413:'\u9C3D',63414:'\u9C36',63415:'\u9DDB',63416:'\u9DD2',63417:'\u9DDE',63418:'\u9DDA',63419:'\u9DCB',63420:'\u9DD0',63421:'\u9DDC',63422:'\u9DD1',63423:'\u9DDF',63424:'\u9DE9',63425:'\u9DD9',63426:'\u9DD8',63427:'\u9DD6',63428:'\u9DF5',63429:'\u9DD5',63430:'\u9DDD',63431:'\u9EB6',63432:'\u9EF0',63433:'\u9F35',63434:'\u9F33',63435:'\u9F32',63436:'\u9F42',63437:'\u9F6B',63438:'\u9F95',63439:'\u9FA2',63440:'\u513D',63441:'\u5299',63442:'\u58E8',63443:'\u58E7',63444:'\u5972',63445:'\u5B4D',63446:'\u5DD8',63447:'\u882F',63448:'\u5F4F',63449:'\u6201',63450:'\u6203',63451:'\u6204',63452:'\u6529',63453:'\u6525',63454:'\u6596',63455:'\u66EB',63456:'\u6B11',63457:'\u6B12',63458:'\u6B0F',63459:'\u6BCA',63460:'\u705B',63461:'\u705A',63462:'\u7222',63463:'\u7382',63464:'\u7381',63465:'\u7383',63466:'\u7670',63467:'\u77D4',63468:'\u7C67',63469:'\u7C66',63470:'\u7E95',63471:'\u826C',63472:'\u863A',63473:'\u8640',63474:'\u8639',63475:'\u863C',63476:'\u8631',63477:'\u863B',63478:'\u863E',63479:'\u8830',63480:'\u8832',63481:'\u882E',63482:'\u8833',63483:'\u8976',63484:'\u8974',63485:'\u8973',63486:'\u89FE',63552:'\u8B8C',63553:'\u8B8E',63554:'\u8B8B',63555:'\u8B88',63556:'\u8C45',63557:'\u8D19',63558:'\u8E98',63559:'\u8F64',63560:'\u8F63',63561:'\u91BC',63562:'\u9462',63563:'\u9455',63564:'\u945D',63565:'\u9457',63566:'\u945E',63567:'\u97C4',63568:'\u97C5',63569:'\u9800',63570:'\u9A56',63571:'\u9A59',63572:'\u9B1E',63573:'\u9B1F',63574:'\u9B20',63575:'\u9C52',63576:'\u9C58',63577:'\u9C50',63578:'\u9C4A',63579:'\u9C4D',63580:'\u9C4B',63581:'\u9C55',63582:'\u9C59',63583:'\u9C4C',63584:'\u9C4E',63585:'\u9DFB',63586:'\u9DF7',63587:'\u9DEF',63588:'\u9DE3',63589:'\u9DEB',63590:'\u9DF8',63591:'\u9DE4',63592:'\u9DF6',63593:'\u9DE1',63594:'\u9DEE',63595:'\u9DE6',63596:'\u9DF2',63597:'\u9DF0',63598:'\u9DE2',63599:'\u9DEC',63600:'\u9DF4',63601:'\u9DF3',63602:'\u9DE8',63603:'\u9DED',63604:'\u9EC2',63605:'\u9ED0',63606:'\u9EF2',63607:'\u9EF3',63608:'\u9F06',63609:'\u9F1C',63610:'\u9F38',63611:'\u9F37',63612:'\u9F36',63613:'\u9F43',63614:'\u9F4F',63649:'\u9F71',63650:'\u9F70',63651:'\u9F6E',63652:'\u9F6F',63653:'\u56D3',63654:'\u56CD',63655:'\u5B4E',63656:'\u5C6D',63657:'\u652D',63658:'\u66ED',63659:'\u66EE',63660:'\u6B13',63661:'\u705F',63662:'\u7061',63663:'\u705D',63664:'\u7060',63665:'\u7223',63666:'\u74DB',63667:'\u74E5',63668:'\u77D5',63669:'\u7938',63670:'\u79B7',63671:'\u79B6',63672:'\u7C6A',63673:'\u7E97',63674:'\u7F89',63675:'\u826D',63676:'\u8643',63677:'\u8838',63678:'\u8837',63679:'\u8835',63680:'\u884B',63681:'\u8B94',63682:'\u8B95',63683:'\u8E9E',63684:'\u8E9F',63685:'\u8EA0',63686:'\u8E9D',63687:'\u91BE',63688:'\u91BD',63689:'\u91C2',63690:'\u946B',63691:'\u9468',63692:'\u9469',63693:'\u96E5',63694:'\u9746',63695:'\u9743',63696:'\u9747',63697:'\u97C7',63698:'\u97E5',63699:'\u9A5E',63700:'\u9AD5',63701:'\u9B59',63702:'\u9C63',63703:'\u9C67',63704:'\u9C66',63705:'\u9C62',63706:'\u9C5E',63707:'\u9C60',63708:'\u9E02',63709:'\u9DFE',63710:'\u9E07',63711:'\u9E03',63712:'\u9E06',63713:'\u9E05',63714:'\u9E00',63715:'\u9E01',63716:'\u9E09',63717:'\u9DFF',63718:'\u9DFD',63719:'\u9E04',63720:'\u9EA0',63721:'\u9F1E',63722:'\u9F46',63723:'\u9F74',63724:'\u9F75',63725:'\u9F76',63726:'\u56D4',63727:'\u652E',63728:'\u65B8',63729:'\u6B18',63730:'\u6B19',63731:'\u6B17',63732:'\u6B1A',63733:'\u7062',63734:'\u7226',63735:'\u72AA',63736:'\u77D8',63737:'\u77D9',63738:'\u7939',63739:'\u7C69',63740:'\u7C6B',63741:'\u7CF6',63742:'\u7E9A',63808:'\u7E98',63809:'\u7E9B',63810:'\u7E99',63811:'\u81E0',63812:'\u81E1',63813:'\u8646',63814:'\u8647',63815:'\u8648',63816:'\u8979',63817:'\u897A',63818:'\u897C',63819:'\u897B',63820:'\u89FF',63821:'\u8B98',63822:'\u8B99',63823:'\u8EA5',63824:'\u8EA4',63825:'\u8EA3',63826:'\u946E',63827:'\u946D',63828:'\u946F',63829:'\u9471',63830:'\u9473',63831:'\u9749',63832:'\u9872',63833:'\u995F',63834:'\u9C68',63835:'\u9C6E',63836:'\u9C6D',63837:'\u9E0B',63838:'\u9E0D',63839:'\u9E10',63840:'\u9E0F',63841:'\u9E12',63842:'\u9E11',63843:'\u9EA1',63844:'\u9EF5',63845:'\u9F09',63846:'\u9F47',63847:'\u9F78',63848:'\u9F7B',63849:'\u9F7A',63850:'\u9F79',63851:'\u571E',63852:'\u7066',63853:'\u7C6F',63854:'\u883C',63855:'\u8DB2',63856:'\u8EA6',63857:'\u91C3',63858:'\u9474',63859:'\u9478',63860:'\u9476',63861:'\u9475',63862:'\u9A60',63863:'\u9C74',63864:'\u9C73',63865:'\u9C71',63866:'\u9C75',63867:'\u9E14',63868:'\u9E13',63869:'\u9EF6',63870:'\u9F0A',63905:'\u9FA4',63906:'\u7068',63907:'\u7065',63908:'\u7CF7',63909:'\u866A',63910:'\u883E',63911:'\u883D',63912:'\u883F',63913:'\u8B9E',63914:'\u8C9C',63915:'\u8EA9',63916:'\u8EC9',63917:'\u974B',63918:'\u9873',63919:'\u9874',63920:'\u98CC',63921:'\u9961',63922:'\u99AB',63923:'\u9A64',63924:'\u9A66',63925:'\u9A67',63926:'\u9B24',63927:'\u9E15',63928:'\u9E17',63929:'\u9F48',63930:'\u6207',63931:'\u6B1E',63932:'\u7227',63933:'\u864C',63934:'\u8EA8',63935:'\u9482',63936:'\u9480',63937:'\u9481',63938:'\u9A69',63939:'\u9A68',63940:'\u9B2E',63941:'\u9E19',63942:'\u7229',63943:'\u864B',63944:'\u8B9F',63945:'\u9483',63946:'\u9C79',63947:'\u9EB7',63948:'\u7675',63949:'\u9A6B',63950:'\u9C7A',63951:'\u9E1D',63952:'\u7069',63953:'\u706A',63954:'\u9EA4',63955:'\u9F7E',63956:'\u9F49',63957:'\u9F98',63958:'\u7881',63959:'\u92B9',63960:'\u88CF',63961:'\u58BB',63962:'\u6052',63963:'\u7CA7',63964:'\u5AFA',63965:'\u2554',63966:'\u2566',63967:'\u2557',63968:'\u2560',63969:'\u256C',63970:'\u2563',63971:'\u255A',63972:'\u2569',63973:'\u255D',63974:'\u2552',63975:'\u2564',63976:'\u2555',63977:'\u255E',63978:'\u256A',63979:'\u2561',63980:'\u2558',63981:'\u2567',63982:'\u255B',63983:'\u2553',63984:'\u2565',63985:'\u2556',63986:'\u255F',63987:'\u256B',63988:'\u2562',63989:'\u2559',63990:'\u2568',63991:'\u255C',63992:'\u2551',63993:'\u2550',63994:'\u256D',63995:'\u256E',63996:'\u2570',63997:'\u256F',63998:'\u2593',64064:'\uE000',64065:'\uE001',64066:'\uE002',64067:'\uE003',64068:'\uE004',64069:'\uE005',64070:'\uE006',64071:'\uE007',64072:'\uE008',64073:'\uE009',64074:'\uE00A',64075:'\uE00B',64076:'\uE00C',64077:'\uE00D',64078:'\uE00E',64079:'\uE00F',64080:'\uE010',64081:'\uE011',64082:'\uE012',64083:'\uE013',64084:'\uE014',64085:'\uE015',64086:'\uE016',64087:'\uE017',64088:'\uE018',64089:'\uE019',64090:'\uE01A',64091:'\uE01B',64092:'\uE01C',64093:'\uE01D',64094:'\uE01E',64095:'\uE01F',64096:'\uE020',64097:'\uE021',64098:'\uE022',64099:'\uE023',64100:'\uE024',64101:'\uE025',64102:'\uE026',64103:'\uE027',64104:'\uE028',64105:'\uE029',64106:'\uE02A',64107:'\uE02B',64108:'\uE02C',64109:'\uE02D',64110:'\uE02E',64111:'\uE02F',64112:'\uE030',64113:'\uE031',64114:'\uE032',64115:'\uE033',64116:'\uE034',64117:'\uE035',64118:'\uE036',64119:'\uE037',64120:'\uE038',64121:'\uE039',64122:'\uE03A',64123:'\uE03B',64124:'\uE03C',64125:'\uE03D',64126:'\uE03E',64161:'\uE03F',64162:'\uE040',64163:'\uE041',64164:'\uE042',64165:'\uE043',64166:'\uE044',64167:'\uE045',64168:'\uE046',64169:'\uE047',64170:'\uE048',64171:'\uE049',64172:'\uE04A',64173:'\uE04B',64174:'\uE04C',64175:'\uE04D',64176:'\uE04E',64177:'\uE04F',64178:'\uE050',64179:'\uE051',64180:'\uE052',64181:'\uE053',64182:'\uE054',64183:'\uE055',64184:'\uE056',64185:'\uE057',64186:'\uE058',64187:'\uE059',64188:'\uE05A',64189:'\uE05B',64190:'\uE05C',64191:'\uE05D',64192:'\uE05E',64193:'\uE05F',64194:'\uE060',64195:'\uE061',64196:'\uE062',64197:'\uE063',64198:'\uE064',64199:'\uE065',64200:'\uE066',64201:'\uE067',64202:'\uE068',64203:'\uE069',64204:'\uE06A',64205:'\uE06B',64206:'\uE06C',64207:'\uE06D',64208:'\uE06E',64209:'\uE06F',64210:'\uE070',64211:'\uE071',64212:'\uE072',64213:'\uE073',64214:'\uE074',64215:'\uE075',64216:'\uE076',64217:'\uE077',64218:'\uE078',64219:'\uE079',64220:'\uE07A',64221:'\uE07B',64222:'\uE07C',64223:'\uE07D',64224:'\uE07E',64225:'\uE07F',64226:'\uE080',64227:'\uE081',64228:'\uE082',64229:'\uE083',64230:'\uE084',64231:'\uE085',64232:'\uE086',64233:'\uE087',64234:'\uE088',64235:'\uE089',64236:'\uE08A',64237:'\uE08B',64238:'\uE08C',64239:'\uE08D',64240:'\uE08E',64241:'\uE08F',64242:'\uE090',64243:'\uE091',64244:'\uE092',64245:'\uE093',64246:'\uE094',64247:'\uE095',64248:'\uE096',64249:'\uE097',64250:'\uE098',64251:'\uE099',64252:'\uE09A',64253:'\uE09B',64254:'\uE09C',64320:'\uE09D',64321:'\uE09E',64322:'\uE09F',64323:'\uE0A0',64324:'\uE0A1',64325:'\uE0A2',64326:'\uE0A3',64327:'\uE0A4',64328:'\uE0A5',64329:'\uE0A6',64330:'\uE0A7',64331:'\uE0A8',64332:'\uE0A9',64333:'\uE0AA',64334:'\uE0AB',64335:'\uE0AC',64336:'\uE0AD',64337:'\uE0AE',64338:'\uE0AF',64339:'\uE0B0',64340:'\uE0B1',64341:'\uE0B2',64342:'\uE0B3',64343:'\uE0B4',64344:'\uE0B5',64345:'\uE0B6',64346:'\uE0B7',64347:'\uE0B8',64348:'\uE0B9',64349:'\uE0BA',64350:'\uE0BB',64351:'\uE0BC',64352:'\uE0BD',64353:'\uE0BE',64354:'\uE0BF',64355:'\uE0C0',64356:'\uE0C1',64357:'\uE0C2',64358:'\uE0C3',64359:'\uE0C4',64360:'\uE0C5',64361:'\uE0C6',64362:'\uE0C7',64363:'\uE0C8',64364:'\uE0C9',64365:'\uE0CA',64366:'\uE0CB',64367:'\uE0CC',64368:'\uE0CD',64369:'\uE0CE',64370:'\uE0CF',64371:'\uE0D0',64372:'\uE0D1',64373:'\uE0D2',64374:'\uE0D3',64375:'\uE0D4',64376:'\uE0D5',64377:'\uE0D6',64378:'\uE0D7',64379:'\uE0D8',64380:'\uE0D9',64381:'\uE0DA',64382:'\uE0DB',64417:'\uE0DC',64418:'\uE0DD',64419:'\uE0DE',64420:'\uE0DF',64421:'\uE0E0',64422:'\uE0E1',64423:'\uE0E2',64424:'\uE0E3',64425:'\uE0E4',64426:'\uE0E5',64427:'\uE0E6',64428:'\uE0E7',64429:'\uE0E8',64430:'\uE0E9',64431:'\uE0EA',64432:'\uE0EB',64433:'\uE0EC',64434:'\uE0ED',64435:'\uE0EE',64436:'\uE0EF',64437:'\uE0F0',64438:'\uE0F1',64439:'\uE0F2',64440:'\uE0F3',64441:'\uE0F4',64442:'\uE0F5',64443:'\uE0F6',64444:'\uE0F7',64445:'\uE0F8',64446:'\uE0F9',64447:'\uE0FA',64448:'\uE0FB',64449:'\uE0FC',64450:'\uE0FD',64451:'\uE0FE',64452:'\uE0FF',64453:'\uE100',64454:'\uE101',64455:'\uE102',64456:'\uE103',64457:'\uE104',64458:'\uE105',64459:'\uE106',64460:'\uE107',64461:'\uE108',64462:'\uE109',64463:'\uE10A',64464:'\uE10B',64465:'\uE10C',64466:'\uE10D',64467:'\uE10E',64468:'\uE10F',64469:'\uE110',64470:'\uE111',64471:'\uE112',64472:'\uE113',64473:'\uE114',64474:'\uE115',64475:'\uE116',64476:'\uE117',64477:'\uE118',64478:'\uE119',64479:'\uE11A',64480:'\uE11B',64481:'\uE11C',64482:'\uE11D',64483:'\uE11E',64484:'\uE11F',64485:'\uE120',64486:'\uE121',64487:'\uE122',64488:'\uE123',64489:'\uE124',64490:'\uE125',64491:'\uE126',64492:'\uE127',64493:'\uE128',64494:'\uE129',64495:'\uE12A',64496:'\uE12B',64497:'\uE12C',64498:'\uE12D',64499:'\uE12E',64500:'\uE12F',64501:'\uE130',64502:'\uE131',64503:'\uE132',64504:'\uE133',64505:'\uE134',64506:'\uE135',64507:'\uE136',64508:'\uE137',64509:'\uE138',64510:'\uE139',64576:'\uE13A',64577:'\uE13B',64578:'\uE13C',64579:'\uE13D',64580:'\uE13E',64581:'\uE13F',64582:'\uE140',64583:'\uE141',64584:'\uE142',64585:'\uE143',64586:'\uE144',64587:'\uE145',64588:'\uE146',64589:'\uE147',64590:'\uE148',64591:'\uE149',64592:'\uE14A',64593:'\uE14B',64594:'\uE14C',64595:'\uE14D',64596:'\uE14E',64597:'\uE14F',64598:'\uE150',64599:'\uE151',64600:'\uE152',64601:'\uE153',64602:'\uE154',64603:'\uE155',64604:'\uE156',64605:'\uE157',64606:'\uE158',64607:'\uE159',64608:'\uE15A',64609:'\uE15B',64610:'\uE15C',64611:'\uE15D',64612:'\uE15E',64613:'\uE15F',64614:'\uE160',64615:'\uE161',64616:'\uE162',64617:'\uE163',64618:'\uE164',64619:'\uE165',64620:'\uE166',64621:'\uE167',64622:'\uE168',64623:'\uE169',64624:'\uE16A',64625:'\uE16B',64626:'\uE16C',64627:'\uE16D',64628:'\uE16E',64629:'\uE16F',64630:'\uE170',64631:'\uE171',64632:'\uE172',64633:'\uE173',64634:'\uE174',64635:'\uE175',64636:'\uE176',64637:'\uE177',64638:'\uE178',64673:'\uE179',64674:'\uE17A',64675:'\uE17B',64676:'\uE17C',64677:'\uE17D',64678:'\uE17E',64679:'\uE17F',64680:'\uE180',64681:'\uE181',64682:'\uE182',64683:'\uE183',64684:'\uE184',64685:'\uE185',64686:'\uE186',64687:'\uE187',64688:'\uE188',64689:'\uE189',64690:'\uE18A',64691:'\uE18B',64692:'\uE18C',64693:'\uE18D',64694:'\uE18E',64695:'\uE18F',64696:'\uE190',64697:'\uE191',64698:'\uE192',64699:'\uE193',64700:'\uE194',64701:'\uE195',64702:'\uE196',64703:'\uE197',64704:'\uE198',64705:'\uE199',64706:'\uE19A',64707:'\uE19B',64708:'\uE19C',64709:'\uE19D',64710:'\uE19E',64711:'\uE19F',64712:'\uE1A0',64713:'\uE1A1',64714:'\uE1A2',64715:'\uE1A3',64716:'\uE1A4',64717:'\uE1A5',64718:'\uE1A6',64719:'\uE1A7',64720:'\uE1A8',64721:'\uE1A9',64722:'\uE1AA',64723:'\uE1AB',64724:'\uE1AC',64725:'\uE1AD',64726:'\uE1AE',64727:'\uE1AF',64728:'\uE1B0',64729:'\uE1B1',64730:'\uE1B2',64731:'\uE1B3',64732:'\uE1B4',64733:'\uE1B5',64734:'\uE1B6',64735:'\uE1B7',64736:'\uE1B8',64737:'\uE1B9',64738:'\uE1BA',64739:'\uE1BB',64740:'\uE1BC',64741:'\uE1BD',64742:'\uE1BE',64743:'\uE1BF',64744:'\uE1C0',64745:'\uE1C1',64746:'\uE1C2',64747:'\uE1C3',64748:'\uE1C4',64749:'\uE1C5',64750:'\uE1C6',64751:'\uE1C7',64752:'\uE1C8',64753:'\uE1C9',64754:'\uE1CA',64755:'\uE1CB',64756:'\uE1CC',64757:'\uE1CD',64758:'\uE1CE',64759:'\uE1CF',64760:'\uE1D0',64761:'\uE1D1',64762:'\uE1D2',64763:'\uE1D3',64764:'\uE1D4',64765:'\uE1D5',64766:'\uE1D6',64832:'\uE1D7',64833:'\uE1D8',64834:'\uE1D9',64835:'\uE1DA',64836:'\uE1DB',64837:'\uE1DC',64838:'\uE1DD',64839:'\uE1DE',64840:'\uE1DF',64841:'\uE1E0',64842:'\uE1E1',64843:'\uE1E2',64844:'\uE1E3',64845:'\uE1E4',64846:'\uE1E5',64847:'\uE1E6',64848:'\uE1E7',64849:'\uE1E8',64850:'\uE1E9',64851:'\uE1EA',64852:'\uE1EB',64853:'\uE1EC',64854:'\uE1ED',64855:'\uE1EE',64856:'\uE1EF',64857:'\uE1F0',64858:'\uE1F1',64859:'\uE1F2',64860:'\uE1F3',64861:'\uE1F4',64862:'\uE1F5',64863:'\uE1F6',64864:'\uE1F7',64865:'\uE1F8',64866:'\uE1F9',64867:'\uE1FA',64868:'\uE1FB',64869:'\uE1FC',64870:'\uE1FD',64871:'\uE1FE',64872:'\uE1FF',64873:'\uE200',64874:'\uE201',64875:'\uE202',64876:'\uE203',64877:'\uE204',64878:'\uE205',64879:'\uE206',64880:'\uE207',64881:'\uE208',64882:'\uE209',64883:'\uE20A',64884:'\uE20B',64885:'\uE20C',64886:'\uE20D',64887:'\uE20E',64888:'\uE20F',64889:'\uE210',64890:'\uE211',64891:'\uE212',64892:'\uE213',64893:'\uE214',64894:'\uE215',64929:'\uE216',64930:'\uE217',64931:'\uE218',64932:'\uE219',64933:'\uE21A',64934:'\uE21B',64935:'\uE21C',64936:'\uE21D',64937:'\uE21E',64938:'\uE21F',64939:'\uE220',64940:'\uE221',64941:'\uE222',64942:'\uE223',64943:'\uE224',64944:'\uE225',64945:'\uE226',64946:'\uE227',64947:'\uE228',64948:'\uE229',64949:'\uE22A',64950:'\uE22B',64951:'\uE22C',64952:'\uE22D',64953:'\uE22E',64954:'\uE22F',64955:'\uE230',64956:'\uE231',64957:'\uE232',64958:'\uE233',64959:'\uE234',64960:'\uE235',64961:'\uE236',64962:'\uE237',64963:'\uE238',64964:'\uE239',64965:'\uE23A',64966:'\uE23B',64967:'\uE23C',64968:'\uE23D',64969:'\uE23E',64970:'\uE23F',64971:'\uE240',64972:'\uE241',64973:'\uE242',64974:'\uE243',64975:'\uE244',64976:'\uE245',64977:'\uE246',64978:'\uE247',64979:'\uE248',64980:'\uE249',64981:'\uE24A',64982:'\uE24B',64983:'\uE24C',64984:'\uE24D',64985:'\uE24E',64986:'\uE24F',64987:'\uE250',64988:'\uE251',64989:'\uE252',64990:'\uE253',64991:'\uE254',64992:'\uE255',64993:'\uE256',64994:'\uE257',64995:'\uE258',64996:'\uE259',64997:'\uE25A',64998:'\uE25B',64999:'\uE25C',65000:'\uE25D',65001:'\uE25E',65002:'\uE25F',65003:'\uE260',65004:'\uE261',65005:'\uE262',65006:'\uE263',65007:'\uE264',65008:'\uE265',65009:'\uE266',65010:'\uE267',65011:'\uE268',65012:'\uE269',65013:'\uE26A',65014:'\uE26B',65015:'\uE26C',65016:'\uE26D',65017:'\uE26E',65018:'\uE26F',65019:'\uE270',65020:'\uE271',65021:'\uE272',65022:'\uE273',65088:'\uE274',65089:'\uE275',65090:'\uE276',65091:'\uE277',65092:'\uE278',65093:'\uE279',65094:'\uE27A',65095:'\uE27B',65096:'\uE27C',65097:'\uE27D',65098:'\uE27E',65099:'\uE27F',65100:'\uE280',65101:'\uE281',65102:'\uE282',65103:'\uE283',65104:'\uE284',65105:'\uE285',65106:'\uE286',65107:'\uE287',65108:'\uE288',65109:'\uE289',65110:'\uE28A',65111:'\uE28B',65112:'\uE28C',65113:'\uE28D',65114:'\uE28E',65115:'\uE28F',65116:'\uE290',65117:'\uE291',65118:'\uE292',65119:'\uE293',65120:'\uE294',65121:'\uE295',65122:'\uE296',65123:'\uE297',65124:'\uE298',65125:'\uE299',65126:'\uE29A',65127:'\uE29B',65128:'\uE29C',65129:'\uE29D',65130:'\uE29E',65131:'\uE29F',65132:'\uE2A0',65133:'\uE2A1',65134:'\uE2A2',65135:'\uE2A3',65136:'\uE2A4',65137:'\uE2A5',65138:'\uE2A6',65139:'\uE2A7',65140:'\uE2A8',65141:'\uE2A9',65142:'\uE2AA',65143:'\uE2AB',65144:'\uE2AC',65145:'\uE2AD',65146:'\uE2AE',65147:'\uE2AF',65148:'\uE2B0',65149:'\uE2B1',65150:'\uE2B2',65185:'\uE2B3',65186:'\uE2B4',65187:'\uE2B5',65188:'\uE2B6',65189:'\uE2B7',65190:'\uE2B8',65191:'\uE2B9',65192:'\uE2BA',65193:'\uE2BB',65194:'\uE2BC',65195:'\uE2BD',65196:'\uE2BE',65197:'\uE2BF',65198:'\uE2C0',65199:'\uE2C1',65200:'\uE2C2',65201:'\uE2C3',65202:'\uE2C4',65203:'\uE2C5',65204:'\uE2C6',65205:'\uE2C7',65206:'\uE2C8',65207:'\uE2C9',65208:'\uE2CA',65209:'\uE2CB',65210:'\uE2CC',65211:'\uE2CD',65212:'\uE2CE',65213:'\uE2CF',65214:'\uE2D0',65215:'\uE2D1',65216:'\uE2D2',65217:'\uE2D3',65218:'\uE2D4',65219:'\uE2D5',65220:'\uE2D6',65221:'\uE2D7',65222:'\uE2D8',65223:'\uE2D9',65224:'\uE2DA',65225:'\uE2DB',65226:'\uE2DC',65227:'\uE2DD',65228:'\uE2DE',65229:'\uE2DF',65230:'\uE2E0',65231:'\uE2E1',65232:'\uE2E2',65233:'\uE2E3',65234:'\uE2E4',65235:'\uE2E5',65236:'\uE2E6',65237:'\uE2E7',65238:'\uE2E8',65239:'\uE2E9',65240:'\uE2EA',65241:'\uE2EB',65242:'\uE2EC',65243:'\uE2ED',65244:'\uE2EE',65245:'\uE2EF',65246:'\uE2F0',65247:'\uE2F1',65248:'\uE2F2',65249:'\uE2F3',65250:'\uE2F4',65251:'\uE2F5',65252:'\uE2F6',65253:'\uE2F7',65254:'\uE2F8',65255:'\uE2F9',65256:'\uE2FA',65257:'\uE2FB',65258:'\uE2FC',65259:'\uE2FD',65260:'\uE2FE',65261:'\uE2FF',65262:'\uE300',65263:'\uE301',65264:'\uE302',65265:'\uE303',65266:'\uE304',65267:'\uE305',65268:'\uE306',65269:'\uE307',65270:'\uE308',65271:'\uE309',65272:'\uE30A',65273:'\uE30B',65274:'\uE30C',65275:'\uE30D',65276:'\uE30E',65277:'\uE30F',65278:'\uE310',129:None,130:None,131:None,132:None,133:None,134:None,135:None,136:None,137:None,138:None,139:None,140:None,141:None,142:None,143:None,144:None,145:None,146:None,147:None,148:None,149:None,150:None,151:None,152:None,153:None,154:None,155:None,156:None,157:None,158:None,159:None,160:None,161:None,162:None,163:None,164:None,165:None,166:None,167:None,168:None,169:None,170:None,171:None,172:None,173:None,174:None,175:None,176:None,177:None,178:None,179:None,180:None,181:None,182:None,183:None,184:None,185:None,186:None,187:None,188:None,189:None,190:None,191:None,192:None,193:None,194:None,195:None,196:None,197:None,198:None,199:None,200:None,201:None,202:None,203:None,204:None,205:None,206:None,207:None,208:None,209:None,210:None,211:None,212:None,213:None,214:None,215:None,216:None,217:None,218:None,219:None,220:None,221:None,222:None,223:None,224:None,225:None,226:None,227:None,228:None,229:None,230:None,231:None,232:None,233:None,234:None,235:None,236:None,237:None,238:None,239:None,240:None,241:None,242:None,243:None,244:None,245:None,246:None,247:None,248:None,249:None,250:None,251:None,252:None,253:None,254:None}
\ No newline at end of file
diff --git a/extract_msg/encoding/utils.py b/extract_msg/encoding/utils.py
new file mode 100644
index 00000000..8aa60ca0
--- /dev/null
+++ b/extract_msg/encoding/utils.py
@@ -0,0 +1,327 @@
+"""
+Internal utilities for extract_msg.encoding.
+"""
+
+__all__ = [
+ 'variableByteDecode',
+ 'variableByteEncode',
+]
+
+
+import codecs
+
+from typing import Dict, Tuple
+
+
+def createVBEncoding(codecName : str, decodingTable : Dict[int, str]) -> codecs.CodecInfo:
+ """
+ Creates the classes for a variable byte encoding, returning the CodecInfo
+ instance associated with it. Currently only supports encodings with up to 2
+ bytes per character.
+
+ :param codecName: The name of the codec being created.
+ :param decodingTable: The table to use for decoding data. Will be used to
+ create the encodingTable.
+ """
+ # Create the encoding table. If there are duplicate keys, they will use the
+ # last one introduced. Iterating in reverse means the lowest possible value
+ # for a character will be the one used.
+ encodingTable = {
+ value : bytes((key,)) if key < 256 else bytes((key >> 8, key & 0xFF))
+ for key, value in reversed(decodingTable.items()) if value is not None
+ }
+
+ # Create the classes.
+ class Codec(codecs.Codec):
+ def encode(self, text, errors='strict'):
+ return variableByteEncode(codecName, text, errors, encodingTable)
+
+ def decode(self, data, errors='strict'):
+ return variableByteDecode(codecName, data, errors, decodingTable)
+
+ class IncrementalEncoder(codecs.IncrementalEncoder):
+ def encode(self, text, final=False):
+ return variableByteEncode(codecName, text, self.errors, encodingTable)[0]
+
+ class IncrementalDecoder(codecs.IncrementalDecoder):
+ def decode(self, data, final=False):
+ return variableByteDecode(codecName, data, self.errors, decodingTable)[0]
+
+ class StreamWriter(Codec, codecs.StreamWriter):
+ pass
+
+ class StreamReader(Codec, codecs.StreamReader):
+ pass
+
+ # Return the CodecInfo instance.
+ return codecs.CodecInfo(
+ name=codecName,
+ encode=Codec().encode,
+ decode=Codec().decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamwriter=StreamWriter,
+ streamreader=StreamReader,
+ )
+
+
+def createSBEncoding(codecName : str, decodingTable : Dict[int, str]) -> codecs.CodecInfo:
+ """
+ Creates the classes for a single byte encoding, returning the CodecInfo
+ instance associated with it. Currently only supports encodings with up to 2
+ bytes per character.
+
+ :param codecName: The name of the codec being created.
+ :param decodingTable: The table to use for decoding data. Will be used to
+ create the encodingTable.
+ """
+ # Create the encoding table. If there are duplicate keys, they will use the
+ # last one introduced. Iterating in reverse means the lowest possible value
+ # for a character will be the one used.
+ encodingTable = {
+ value : bytes((key,))
+ for key, value in reversed(decodingTable.items()) if value is not None
+ }
+
+ # Create the classes.
+ class Codec(codecs.Codec):
+ def encode(self, text, errors='strict'):
+ return singleByteEncode(codecName, text, errors, encodingTable)
+
+ def decode(self, data, errors='strict'):
+ return singleByteDecode(codecName, data, errors, decodingTable)
+
+ class IncrementalEncoder(codecs.IncrementalEncoder):
+ def encode(self, text, final=False):
+ return singleByteEncode(codecName, text, self.errors, encodingTable)[0]
+
+ class IncrementalDecoder(codecs.IncrementalDecoder):
+ def decode(self, data, final=False):
+ return singleByteDecode(codecName, data, self.errors, decodingTable)[0]
+
+ class StreamWriter(Codec, codecs.StreamWriter):
+ pass
+
+ class StreamReader(Codec, codecs.StreamReader):
+ pass
+
+ # Return the CodecInfo instance.
+ return codecs.CodecInfo(
+ name=codecName,
+ encode=Codec().encode,
+ decode=Codec().decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamwriter=StreamWriter,
+ streamreader=StreamReader,
+ )
+
+
+def singleByteDecode(codecName : str, data, errors : str, decodeTable : Dict[int, str]) -> Tuple[str, int]:
+ """
+ Function for decoding single-byte codecs.
+
+ :param codecName: The name of the codec, used for error messages.
+ :param data: A bytes-like object to decode.
+ :param errors: The error behavior to use.
+ :param decodeTable: The mapping of values to use. Continuation bytes MUST be
+ defined in the table, but SHOULD be set to None. This allows for the
+ function to detect what bytes are valid for continuation.
+ """
+ if len(data) == 0:
+ return ('', 0)
+
+ errorHandler = codecs.lookup_error(errors)
+ output = ''
+
+ iterator = enumerate(data)
+ start = 0
+
+ for start, byte in iterator:
+ if byte in decodeTable:
+ output += decodeTable[byte]
+ else:
+ err = UnicodeDecodeError(codecName,
+ data,
+ start,
+ start + 1,
+ 'character maps to ',
+ )
+ rep = errorHandler(err)
+ output += rep[0]
+ for _ in range(rep[1] - start - 1):
+ try:
+ next(iterator)
+ except StopIteration:
+ break
+
+ return (output, len(output))
+
+
+def singleByteEncode(codecName : str, data, errors : str, encodeTable : Dict[str, bytes]) -> Tuple[bytes, int]:
+ """
+ Function for encoding variable-byte codecs that use one or two bytes per
+ character.
+
+ :param codecName: The name of the codec, used for error messages.
+ :param data: A bytes-like object to decode.
+ :param errors: The error behavior to use.
+ :param encodeTable: The mapping of values to use.
+ """
+ if len(data) == 0:
+ return
+
+ errorHandler = codecs.lookup_error(errors)
+ output = b''
+ iterator = enumerate(data)
+ start = 0
+ for start, char in iterator:
+ if char in encodeTable:
+ output += encodeTable[char]
+ else:
+ err = UnicodeEncodeError(codecName,
+ data,
+ start,
+ start + 1,
+ 'illegal multibyte sequence',
+ )
+ rep = errorHandler(err)
+ output += rep[0]
+ # Skip the specified number of characters.
+ for _ in range(rep[1] - start - 1):
+ try:
+ next(iterator)
+ except StopIteration:
+ break
+
+
+
+def variableByteDecode(codecName : str, data, errors : str, decodeTable : Dict[int, str]) -> Tuple[str, int]:
+ """
+ Function for decoding variable-byte codecs that use one or two bytes per character.
+
+ Checks if a character is less than 0x80, mapping it directly if so.
+ Otherwise, it reads the next byte and combines the two before looking up the
+ new value.
+
+ :param codecName: The name of the codec, used for error messages.
+ :param data: A bytes-like object to decode.
+ :param errors: The error behavior to use.
+ :param decodeTable: The mapping of values to use. Continuation bytes MUST be
+ defined in the table, but SHOULD be set to None. This allows for the
+ function to detect what bytes are valid for continuation.
+ """
+ if len(data) == 0:
+ return ('', 0)
+
+ errorHandler = codecs.lookup_error(errors)
+ output = ''
+
+ iterator = enumerate(data)
+ start = 0
+
+ for start, byte in iterator:
+ # Variable byte should be an integer here.
+ if byte < 0x80:
+ if byte in decodeTable:
+ output += decodeTable[byte]
+ else:
+ err = UnicodeDecodeError(codecName,
+ data,
+ start,
+ start + 1,
+ 'character maps to ',
+ )
+ rep = errorHandler(err)
+ output += rep[0]
+ # Skip the specified number of characters.
+ for _ in range(rep[1] - start - 1):
+ try:
+ next(iterator)
+ except StopIteration:
+ break
+ elif byte not in decodeTable:
+ err = UnicodeDecodeError(codecName,
+ data,
+ start,
+ start + 1,
+ 'invalid start byte',
+ )
+ rep = errorHandler(err)
+ output += rep[0]
+ # Skip the specified number of characters.
+ for _ in range(rep[1] - start - 1):
+ try:
+ next(iterator)
+ except StopIteration:
+ break
+
+ else:
+ try:
+ byte = (byte << 8) | next(iterator)[1]
+ if byte in decodeTable:
+ output += decodeTable[byte]
+ else:
+ err = UnicodeDecodeError(codecName,
+ data,
+ start,
+ start + 2,
+ 'character maps to ',
+ )
+ rep = errorHandler(err)
+ output += rep[0]
+ # Skip the specified number of characters.
+ for _ in range(rep[1] - start - 1):
+ next(iterator)
+ except StopIteration:
+ err = UnicodeDecodeError(codecName,
+ data,
+ start,
+ start + 1,
+ 'unexpected end of data',
+ )
+ rep = errorHandler(err)
+ output += rep[0]
+ break
+ # No more data, so that's all that needs to happen.
+ return (output, start)
+
+
+def variableByteEncode(codecName : str, data, errors : str, encodeTable : Dict[str, bytes]) -> Tuple[bytes, int]:
+ """
+ Function for encoding variable-byte codecs that use one or two bytes per
+ character.
+
+ :param codecName: The name of the codec, used for error messages.
+ :param data: A bytes-like object to decode.
+ :param errors: The error behavior to use.
+ :param encodeTable: The mapping of values to use.
+ """
+ if len(data) == 0:
+ return
+
+ errorHandler = codecs.lookup_error(errors)
+ output = b''
+ iterator = enumerate(data)
+ start = 0
+
+ for start, char in iterator:
+ if char in encodeTable:
+ data += encodeTable[char]
+ else:
+ err = UnicodeEncodeError(codecName,
+ data,
+ start,
+ start + 1,
+ 'illegal multibyte sequence',
+ )
+ rep = errorHandler(err)
+ output += rep[0]
+ # Skip the specified number of characters.
+ for _ in range(rep[1] - start - 1):
+ try:
+ next(iterator)
+ except StopIteration:
+ break
+
+ return (output, start + 1)
diff --git a/extract_msg/enums.py b/extract_msg/enums.py
index f5ace9b5..0d81454f 100644
--- a/extract_msg/enums.py
+++ b/extract_msg/enums.py
@@ -1,27 +1,79 @@
__all__ = [
- 'AddressBookType', 'AppointmentAuxilaryFlag', 'AppointmentColor',
- 'AppointmentStateFlag', 'AttachErrorBehavior', 'AttachmentType',
- 'BCImageAlignment', 'BCImageSource', 'BCLabelFormat', 'BCTemplateID',
- 'BCTextFormat', 'BodyTypes', 'BusyStatus', 'ClientIntentFlag', 'Color',
- 'ContactAddressIndex', 'ContactLinkState', 'DeencapType',
- 'DirectoryEntryType', 'DisplayType', 'ElectronicAddressProperties',
- 'EntryIDType', 'EntryIDTypeHex', 'ErrorCode', 'ErrorCodeType', 'Gender',
- 'IconIndex', 'Importance', 'Intelligence', 'MacintoshEncoding',
- 'MeetingObjectChange', 'MeetingRecipientType', 'MeetingType',
- 'MessageFormat', 'MessageType', 'NamedPropertyType', 'OORBodyFormat',
- 'PostalAddressID', 'Priority', 'PropertiesType', 'RecipientRowFlagType',
- 'RecipientType', 'RecurCalendarType', 'RecurDOW', 'RecurEndType',
- 'RecurFrequency', 'RecurMonthNthWeek', 'RecurPatternTypeSpecificWeekday',
- 'RecurPatternType', 'ResponseStatus', 'ResponseType', 'RuleActionType',
- 'Sensitivity', 'ServerProcessingAction', 'SideEffect', 'TaskAcceptance',
- 'TaskHistory', 'TaskMode', 'TaskMultipleRecipients', 'TaskOwnership',
- 'TaskRequestType', 'TaskState', 'TaskStatus', 'TZFlag',
+ 'AddressBookType',
+ 'AppointmentAuxilaryFlag',
+ 'AppointmentColor',
+ 'AppointmentStateFlag',
+ 'AttachmentPermissionType',
+ 'AttachmentType',
+ 'BCImageAlignment',
+ 'BCImageSource',
+ 'BCLabelFormat',
+ 'BCTemplateID',
+ 'BCTextFormat',
+ 'BodyTypes',
+ 'BusyStatus',
+ 'ClientIntentFlag',
+ 'Color',
+ 'ContactAddressIndex',
+ 'ContactLinkState',
+ 'DeencapType',
+ 'DirectoryEntryType',
+ 'DisplayType',
+ 'DVAspect',
+ 'ElectronicAddressProperties',
+ 'EntryIDType',
+ 'EntryIDTypeHex',
+ 'ErrorBehavior',
+ 'ErrorCode',
+ 'ErrorCodeType',
+ 'Gender',
+ 'IconIndex',
+ 'Importance',
+ 'InsecureFeatures',
+ 'Intelligence',
+ 'MacintoshEncoding',
+ 'MeetingObjectChange',
+ 'MeetingRecipientType',
+ 'MeetingType',
+ 'MessageFormat',
+ 'MessageType',
+ 'NamedPropertyType',
+ 'NoteColor',
+ 'OORBodyFormat',
+ 'PostalAddressID',
+ 'Priority',
+ 'PropertiesType',
+ 'RecipientRowFlagType',
+ 'RecipientType',
+ 'RecurCalendarType',
+ 'RecurDOW',
+ 'RecurEndType',
+ 'RecurFrequency',
+ 'RecurMonthNthWeek',
+ 'RecurPatternTypeSpecificWeekday',
+ 'RecurPatternType',
+ 'ResponseStatus',
+ 'ResponseType',
+ 'RuleActionType',
+ 'SaveType',
+ 'Sensitivity',
+ 'ServerProcessingAction',
+ 'SideEffect',
+ 'TaskAcceptance',
+ 'TaskHistory',
+ 'TaskMode',
+ 'TaskMultipleRecipients',
+ 'TaskOwnership',
+ 'TaskRequestType',
+ 'TaskState',
+ 'TaskStatus',
+ 'TZFlag',
]
import enum
-from typing import Set, Union
+from typing import Union
class AddressBookType(enum.Enum):
@@ -43,7 +95,7 @@ class AddressBookType(enum.Enum):
-class AppointmentAuxilaryFlag(enum.Enum):
+class AppointmentAuxilaryFlag(enum.IntFlag):
"""
Describes the auxilary state of the object.
@@ -54,21 +106,6 @@ class AppointmentAuxilaryFlag(enum.Enum):
REPAIR_UPDATE_MESSAGE: The meeting request is a Repair Update Message sent
from a server-side calendar repair system.
"""
- @classmethod
- def fromBits(cls, value : int) -> Set['AppointmentAuxilaryFlag']:
- """
- Takes an int and returns a set of the flags.
- """
- flags = set()
- for x in range(7):
- bit = value & (1 << x)
- if bit:
- if x in (3, 4, 6):
- raise ValueError('Reserved bit was set.')
- flags.add(cls(bit))
-
- return flags
-
COPIED = 0b1
FORCE_MEETING_RESPONSE = 0b10
FORWARDED = 0b100
@@ -91,25 +128,28 @@ class AppointmentColor(enum.Enum):
-class AppointmentStateFlag(enum.Enum):
+class AppointmentStateFlag(enum.IntFlag):
"""
MEETING: The object is a Meeting object or meeting-related object.
RECEIVED: The represented object was received from someone else.
CANCELED: The Meeting object that is represented has been canceled.
"""
- @classmethod
- def fromBits(cls, value : int) -> Set['AppointmentStateFlag']:
- """
- Takes an int and returns a set of the flags.
- """
- return {cls(1 << x) for x in range(3) if (value & (1 << x)) != 0}
-
MEETING = 0b1
RECEIVED = 0b10
CANCELED = 0b100
+class AttachmentPermissionType(enum.Enum):
+ """
+ The permission type data associated with a web reference attachment.
+ """
+ NONE = 0
+ VIEW = 1
+ EDIT = 2
+
+
+
class AttachmentType(enum.Enum):
"""
The type represented by the attachment.
@@ -131,6 +171,7 @@ class AttachmentType(enum.Enum):
BROKEN = 4
UNSUPPORTED = 5
SIGNED_EMBEDDED = 6
+ CUSTOM = 7
UNKNOWN = 0xFFFFFFFF
@@ -271,11 +312,10 @@ class BodyTypes(enum.IntFlag):
method for generated bodies (if you check a body and it is not null, but it
is not listed in the enum, then it was generated from another body).
- This is an IntFlag enum, so to check if a body was found use the & operator
- with the body you are checking and ensuring the result isn't BodyTypes.None.
- You can also convert the result to a bool. For example:
+ This is an IntFlag enum, so to check if a body was found use the in operator
+ with the body you are checking. For example:
- >>> rtfFound = bool(msg.detectedBodies & BodyTypes.RTF)
+ >>> rtfFound = BodyTypes.RTF msg.detectedBodies
"""
NONE = 0b000
PLAIN = 0b001
@@ -304,7 +344,7 @@ class BusyStatus(enum.Enum):
-class ClientIntentFlag(enum.Enum):
+class ClientIntentFlag(enum.IntFlag):
"""
An action a user has taken on a Meeting object.
@@ -327,13 +367,6 @@ class ClientIntentFlag(enum.Enum):
CANCELED: The user canceled a meeting request.
EXCEPTION_CANCELED: The user canceled an exception to a recurring series.
"""
- @classmethod
- def fromBits(cls, value : int) -> Set['ClientIntentFlag']:
- """
- Takes an int and returns a set of the flags.
- """
- return {cls(1 << x) for x in range(13) if (value & (1 << x))}
-
MANAGER = 0b1
DELEGATE = 0b10
DELETED_WITH_NO_RESPONSE = 0b100
@@ -355,6 +388,7 @@ class Color(enum.IntEnum):
BLACK = 1
+
class ContactAddressIndex(enum.Enum):
EMAIL_1 = 0
EMAIL_2 = 1
@@ -415,21 +449,17 @@ class DisplayType(enum.Enum):
-class ElectronicAddressProperties(enum.Enum):
- @classmethod
- def fromBits(cls, value : int) -> Set['ElectronicAddressProperties']:
- """
- Converts an int, with the left most bit referring to 0x00000000, to a
- set of this enum.
+class DVAspect(enum.IntEnum):
+ """
+ Part of the extra data for Outlook signatures. Different sources seem to
+ disagree on the meanings, so I'm sticking to the meanings in the official
+ Microsoft documentation of the DVASPECT enumeration.
+ """
+ CONTENT = 1
+ ICON = 4
- :raises ValueError: The value was less than 0.
- """
- if value < 0:
- raise ValueError('Value must not be negative.')
- # This is a quick compressed way to convert the bits in the int into
- # a tuple of instances of this class should any bit be a 1.
- return {cls(int(index)) for index, val in enumerate(bin(value)[:1:-1]) if val == '1'}
+class ElectronicAddressProperties(enum.Enum):
EMAIL_1 = 0x00000000
EMAIL_2 = 0x00000001
EMAIL_3 = 0x00000002
@@ -489,22 +519,37 @@ def toRaw(self):
class ErrorBehavior(enum.IntFlag):
"""
The behavior to follow when handling an error in an MSG file and it's
- attachments. This is an int flag enum, so the options you want will be ORed
- with each other.
+ attachments. Specifying an option indicates the behavior for the situation
+ is to log a message, if anything, instead of raising an exception. This is
+ an int flag enum, so the options you want will be ORed with each other.
THROW: Throw the exception regardless of type.
ATTACH_NOT_IMPLEMENTED: Silence the exception for NotImplementedError.
ATTACH_BROKEN: Silence the exception for broken attachments.
- ATTACH_SUPPRESS_ALL: Silence the exception for NotImplementedError and for broken
- attachments.
- STANDARDS_VIOLATION
- """
- THROW = 0b000
- ATTACH_NOT_IMPLEMENTED = 0b001
- ATTACH_BROKEN = 0b010
- ATTACH_SUPPRESS_ALL = 0b011
- STANDARDS_VIOLATION = 0b100
- SUPPRESS_ALL = 0b111
+ ATTACH_SUPPRESS_ALL: Silence the exception for NotImplementedError and for
+ broken attachments.
+ RTFDE_MALFORMED: Silences errors about malformed RTF data.
+ RTFDE_UNKNOWN_ERROR: Silences errors from RTFDE that are not normal.
+ RTFDE: Silences all errors from RTFDE.
+ STANDARDS_VIOLATION: Silences StandardViolationError where acceptable.
+ OLE_DEFECT_INCORRECT: Silences defects of type DEFECT_INCORRECT that are
+ enabled by default. This can lead to strange bugs.
+ SUPPRESS_ALL: Silences all of the above.
+ """
+ THROW = 0b000000
+ # Attachments.
+ ATTACH_NOT_IMPLEMENTED = 0b000001
+ ATTACH_BROKEN = 0b000010
+ ATTACH_SUPPRESS_ALL = 0b000011
+ # RTFDE.
+ RTFDE_MALFORMED = 0b000100
+ RTFDE_UNKNOWN_ERROR = 0b001000
+ RTFDE = 0b001100
+ # General.
+ STANDARDS_VIOLATION = 0b010000
+ OLE_DEFECT_INCORRECT = 0b100000
+
+ SUPPRESS_ALL = 0b111111
@@ -1150,6 +1195,29 @@ class Importance(enum.Enum):
+class InsecureFeatures(enum.IntFlag):
+ """
+ Insecure options that can be enabled for an MSG file.
+
+ Using ALL is not recommended unless you check this list before updating to
+ a new version of the module, as new features may have been added. It is
+ also not recommended to use these on files you do not trust.
+
+ The following features are avilable:
+ NONE: No insecure features are allowed (default).
+ PIL_IMAGE_PARSING: Various operations requiring PIL or Pillow that will read
+ image data from parts of the MSG file. These operations are usually
+ constructing new images or are converting from one format to another.
+ This may expose you to security issues from those libraries.
+
+ ALL: All of the previously listed features will be enabled for the MSG file.
+ """
+ NONE = 0b0000
+ PIL_IMAGE_PARSING = 0b0001
+ ALL = 0b1111
+
+
+
class Intelligence(enum.Enum):
ERROR = -1
DUMB = 0
@@ -1168,7 +1236,7 @@ class MacintoshEncoding(enum.Enum):
-class MeetingObjectChange(enum.Enum):
+class MeetingObjectChange(enum.IntFlag):
"""
Indicates a property that has changed on a meeting object.
@@ -1183,23 +1251,6 @@ class MeetingObjectChange(enum.Enum):
RESPONSE: The responseRequested or replyRequested property has changed.
ALLOW_PROPOSE: The appointmentNotAllowPropose property has changed.
"""
- @classmethod
- def fromBits(cls, value : int) -> Set['MeetingObjectChange']:
- """
- Takes an int and returns a set of the changes.
- """
- changes = set()
- for x in range(32):
- if x in (8, 11) or (12 < x < 31):
- continue
- bit = value & (1 << x)
- if bit:
- if x in (12, 31):
- raise ValueError('Reserved bit was set.')
- changes.add(cls(bit))
-
- return changes
-
START = 0b1
END = 0b10
RECUR = 0b100
@@ -1270,6 +1321,15 @@ class NamedPropertyType(enum.Enum):
+class NoteColor(enum.Enum):
+ BLUE = 0
+ GREEN = 1
+ PINK = 2
+ YELLOW = 3
+ WHITE = 4
+
+
+
class OORBodyFormat(enum.Enum):
"""
The body format for One Off Recipients.
@@ -1402,17 +1462,10 @@ class RecurMonthNthWeek(enum.Enum):
-class RecurPatternTypeSpecificWeekday(enum.Enum):
+class RecurPatternTypeSpecificWeekday(enum.IntFlag):
"""
See [MS-OXOCAL] for details.
"""
- @classmethod
- def fromBits(cls, value : int) -> Set['RecurPatternTypeSpecificWeekday']:
- """
- Takes an int and returns a set of the weekdays.
- """
- return {cls(1 << x) for x in range(1, 8) if (value & (1 << x))}
-
SATURDAY = 0b10
FRIDAY = 0b100
THURSDAY = 0b1000
@@ -1483,6 +1536,31 @@ class RuleActionType(enum.Enum):
+class SaveType(enum.Enum):
+ """
+ Specifies the way that a function saved the data. Used to determine how the
+ return value from a save function should be read.
+
+ CUSTOM: An unlisted save method was used, and the second value is
+ unspecified.
+ NONE: No data was saved, and the second tuple value should be None.
+ FILE: A single file was save, and the location is the second value.
+ FILES: Multiple files were created, and the second value is a list of the
+ locations.
+ FOLDER: A folder was created to store data, and the location is the second
+ value.
+ FOLDERS: Multiple folders were created to store data, and the second value
+ is a list of the locations.
+ """
+ CUSTOM = -1
+ NONE = 0
+ FILE = 1
+ FILES = 2
+ FOLDER = 3
+ FOLDERS = 4
+
+
+
class Sensitivity(enum.Enum):
NORMAL = 0
PERSONAL = 1
@@ -1491,17 +1569,10 @@ class Sensitivity(enum.Enum):
-class ServerProcessingAction(enum.Enum):
+class ServerProcessingAction(enum.IntFlag):
"""
Actions taken on a meeting-related object.
"""
- @classmethod
- def fromBits(cls, value : int) -> Set['ServerProcessingAction']:
- """
- Takes an int and returns a set of the weekdays.
- """
- return {cls(1 << x) for x in range(16) if (value & (1 << x))}
-
DELEGATOR_WANTS_COPY = 0x00000002
CREATED_ON_PRINCIPLE = 0x00000010
UPDATED_CAL_ITEM = 0x00000080
@@ -1512,7 +1583,7 @@ def fromBits(cls, value : int) -> Set['ServerProcessingAction']:
-class SideEffect(enum.Enum):
+class SideEffect(enum.IntFlag):
"""
A flag for how a Message object is handled by the client in relation to
certain user interface actions.
@@ -1538,13 +1609,6 @@ class SideEffect(enum.Enum):
OPEN_TO_PERM_DELETE: The client opens the Message object to permanently
delete it.
"""
- @classmethod
- def fromBits(cls, value : int) -> Set['SideEffect']:
- """
- Takes an int and returns a set of the side effects.
- """
- return {cls(1 << x) for x in range(15) if (value & (1 << x))}
-
OPEN_TO_DELETE = 0b1
NO_FRAME = 0b1000
COERCE_TO_INDEX = 0b10000
@@ -1604,14 +1668,7 @@ class TaskMode(enum.Enum):
-class TaskMultipleRecipients(enum.Enum):
- @classmethod
- def fromBits(cls, value : int) -> Set['TaskMultipleRecipients']:
- """
- Takes an int and returns a set of the flags.
- """
- return {cls(1 << x) for x in range(2) if (value & (1 << x))}
-
+class TaskMultipleRecipients(enum.IntFlag):
SENT = 0x00000001
RECEIVED = 0x00000002
@@ -1702,20 +1759,13 @@ class TaskStatus(enum.Enum):
-class TZFlag(enum.Enum):
+class TZFlag(enum.IntFlag):
"""
Flags for a TZRule object as defined in [MS-OXOCAL].
RECUR_CURRENT_TZREG: The rule is associated with a recurring series.
EFFECTIVE_TZREG: The rule is the effective rule.
"""
- @classmethod
- def fromBits(cls, value : int) -> Set['TZFlag']:
- """
- Takes an int and returns a set of the flags.
- """
- return {cls(1 << x) for x in range(2) if (value & (1 << x))}
-
RECUR_CURRENT_TZREG = 0b1
EFFECTIVE_TZREG = 0b10
@@ -1764,7 +1814,7 @@ def __getitem__(self, name):
-# Deprecated Enums
+# Deprecated Enums. These are not exported but may be directly accessed.
AttachErrorBehavior = _EnumDeprecator(
'AttachErrorBehavior',
ErrorBehavior,
diff --git a/extract_msg/exceptions.py b/extract_msg/exceptions.py
index d323746e..17793ac1 100644
--- a/extract_msg/exceptions.py
+++ b/extract_msg/exceptions.py
@@ -7,7 +7,8 @@
"""
__all__ = [
- 'BadHtmlError',
+ 'ExMsgBaseException',
+
'ConversionError',
'DataNotFoundError',
'DeencapMalformedData',
@@ -27,63 +28,74 @@
'WKError',
]
+# Base exception types.
-import logging
-
-
-# Add logger bus.
-logger = logging.getLogger(__name__)
-logger.addHandler(logging.NullHandler())
-
+class ExMsgBaseException(Exception):
+ """
+ The base class for all custom exceptions the module uses.
+ """
-class BadHtmlError(ValueError):
+# I would want this to also be a subclass of NotImplementedError, but Python
+# docs say that CPython can make that a bit problematic due to things from the C
+# side of the code.
+class FeatureNotImplemented(ExMsgBaseException):
"""
- HTML failed to pass validation.
+ The base class for a feature not yet being implemented in the module.
"""
-class ConversionError(Exception):
+# More specific exceptions.
+
+
+class ConversionError(ExMsgBaseException):
"""
An error occured during type conversion.
"""
-class DataNotFoundError(Exception):
+class DataNotFoundError(ExMsgBaseException):
"""
Requested stream type was unavailable.
"""
-class DeencapMalformedData(Exception):
+class DeencapMalformedData(ExMsgBaseException):
"""
Data to deencapsulate was malformed in some way.
"""
-class DeencapNotEncapsulated(Exception):
+class DeencapNotEncapsulated(ExMsgBaseException):
"""
Data to deencapsulate did not contain any encapsulated data.
"""
-class ExecutableNotFound(Exception):
+class ExecutableNotFound(ExMsgBaseException):
"""
Could not find the specified executable.
"""
-class IncompatibleOptionsError(Exception):
+class IncompatibleOptionsError(ExMsgBaseException):
"""
Provided options are incompatible with each other.
"""
-class InvalidFileFormatError(OSError):
+class InvalidFileFormatError(ExMsgBaseException):
"""
An Invalid File Format Error occurred.
"""
-class InvaildPropertyIdError(Exception):
+class InvaildPropertyIdError(ExMsgBaseException):
"""
The provided property ID was invalid.
"""
-class InvalidVersionError(Exception):
+class PrefixError(ExMsgBaseException):
+ """
+ An issue was detected with the provided prefix. This should never occur if
+ you have no manually provided a prefix.
+ """
+
+class SecurityError(ExMsgBaseException):
"""
- The version specified is invalid.
+ A code path was triggered that would use an insecure feature, but that
+ insecure feature was not enabled.
"""
class StandardViolationError(InvalidFileFormatError):
@@ -91,11 +103,11 @@ class StandardViolationError(InvalidFileFormatError):
A critical violation of the MSG standards was detected and could not be
recovered from. Recoverable violations will result in log messages instead.
- Any that could reasonably be skipped, although are likely to still cause
+ Any that could reasonably be skipped, although are likely to still cause
errors down the line, can be suppressed.
"""
-class TZError(Exception):
+class TZError(ExMsgBaseException):
"""
Specifically not an OSError to avoid being caught by parts of the module.
This error represents a fatal error in the datetime parsing as it usually
@@ -105,34 +117,34 @@ class TZError(Exception):
TeamMsgExtractor#169 for information on why you are getting this error.
"""
-class UnknownCodepageError(Exception):
+class UnknownCodepageError(ExMsgBaseException):
"""
The codepage provided was not one we know of.
"""
-class UnsupportedEncodingError(NotImplementedError):
+class UnsupportedEncodingError(FeatureNotImplemented):
"""
The codepage provided is known but is not supported.
"""
-class UnknownTypeError(Exception):
+class UnknownTypeError(ExMsgBaseException):
"""
The type specified is not one that is recognized.
"""
-class UnsupportedMSGTypeError(NotImplementedError):
+class UnsupportedMSGTypeError(FeatureNotImplemented):
"""
An exception that is raised when an MSG class is recognized by not
supported.
"""
-class UnrecognizedMSGTypeError(TypeError):
+class UnrecognizedMSGTypeError(ExMsgBaseException):
"""
An exception that is raised when the module cannot determine how to properly
open a specific class of msg file.
"""
-class WKError(RuntimeError):
+class WKError(ExMsgBaseException):
"""
An error occured while running wkhtmltopdf.
"""
diff --git a/extract_msg/meeting_related.py b/extract_msg/meeting_related.py
deleted file mode 100644
index 983c92cd..00000000
--- a/extract_msg/meeting_related.py
+++ /dev/null
@@ -1,64 +0,0 @@
-__all__ = [
- 'MeetingRelated',
-]
-
-
-import datetime
-
-from typing import Optional, Set
-
-from . import constants
-from .calendar_base import CalendarBase
-from .enums import ServerProcessingAction
-
-
-class MeetingRelated(CalendarBase):
- """
- Base class for meeting-related objects.
- """
-
- @property
- def attendeeCriticalChange(self) -> Optional[datetime.datetime]:
- """
- The date and time at which the meeting-related object was sent.
- """
- return self._ensureSetNamed('_attendeeCriticalChange', '0001', constants.PSETID_MEETING)
-
- @property
- def processed(self) -> bool:
- """
- Indicates whether a client has processed a meeting-related object.
- """
- return self._ensureSetProperty('_processed', '7D01000B', overrideClass = bool, preserveNone = False)
-
- @property
- def serverProcessed(self) -> bool:
- """
- Indicates that the Meeting Request object or Meeting Update object has
- been processed.
- """
- return self._ensureSetNamed('_serverProcessed', '85CC', constants.PSETID_CALENDAR_ASSISTANT, overrideClass = bool, preserveNone = False)
-
- @property
- def serverProcessingActions(self) -> Optional[Set[ServerProcessingAction]]:
- """
- A set of which actions have been taken on the Meeting Request object or
- Meeting Update object.
- """
- return self._ensureSetNamed('_serverProcessingActions', '85CD', constants.PSETID_CALENDAR_ASSISTANT, overrideClass = ServerProcessingAction.fromBits)
-
- @property
- def timeZone(self) -> Optional[int]:
- """
- Specifies information about the time zone of a recurring meeting.
-
- See PidLidTimeZone in [MS-OXOCAL] for details.
- """
- return self._ensureSetNamed('_timeZone', '000C', constants.PSETID_MEETING)
-
- @property
- def where(self) -> Optional[str]:
- """
- PidLidWhere. Should be the same as location.
- """
- return self._ensureSetNamed('_where', '0002', constants.PSETID_MEETING)
diff --git a/extract_msg/message.py b/extract_msg/message.py
deleted file mode 100644
index 33a0fe6f..00000000
--- a/extract_msg/message.py
+++ /dev/null
@@ -1,15 +0,0 @@
-__all__ = [
- 'Message',
-]
-
-
-from .message_base import MessageBase
-
-
-# Due to changes to how saving works, Message is now identical to MessageBase,
-# just with a different name. This is for backwards compatability, type
-# identification, and possible future specializations to it.
-class Message(MessageBase):
- """
- Parser for Microsoft Outlook message files.
- """
diff --git a/extract_msg/message_signed_base.py b/extract_msg/message_signed_base.py
deleted file mode 100644
index 84a8145b..00000000
--- a/extract_msg/message_signed_base.py
+++ /dev/null
@@ -1,189 +0,0 @@
-__all__ = [
- 'MessageSignedBase',
-]
-
-
-import html
-import logging
-import re
-
-from typing import List, Optional
-
-from .enums import ErrorBehavior
-from .exceptions import StandardViolationError
-from .message_base import MessageBase
-from .signed_attachment import SignedAttachment
-from .utils import inputToBytes, inputToString, unwrapMultipart
-
-
-logger = logging.getLogger(__name__)
-logger.addHandler(logging.NullHandler())
-
-
-class MessageSignedBase(MessageBase):
- """
- Base class for Message like msg files.
- """
-
- def __init__(self, path, **kwargs):
- """
- :param path: path to the msg file in the system or is the raw msg file.
- :param prefix: used for extracting embeded msg files
- inside the main one. Do not set manually unless
- you know what you are doing.
- :param attachmentClass: optional, the class the Message object
- will use for attachments. You probably should
- not change this value unless you know what you
- are doing.
- :param signedAttachmentClass: optional, the class the object will use
- for signed attachments.
- :param filename: optional, the filename to be used by default when
- saving.
- :param delayAttachments: optional, delays the initialization of
- attachments until the user attempts to retrieve them. Allows MSG
- files with bad attachments to be initialized so the other data can
- be retrieved.
- :param overrideEncoding: optional, an encoding to use instead of the one
- specified by the msg file. Do not report encoding errors caused by
- this.
- :param attachmentErrorBehavior: Optional, the behavior to use in the
- event of an error when parsing the attachments.
- :param recipientSeparator: Optional, Separator string to use between
- recipients.
- """
- self.__recipientSeparator = kwargs.get('recipientSeparator', ';')
- self.__signedAttachmentClass = kwargs.get('signedAttachmentClass', SignedAttachment)
- super().__init__(path, **kwargs)
- # Initialize properties in the order that is least likely to cause bugs.
- # TODO have each function check for initialization of needed data so these
- # lines will be unnecessary.
- if not kwargs.get('delayAttachments', False):
- self.attachments
-
- @property
- def attachments(self) -> List:
- """
- Returns a list of all attachments.
-
- :raises StandardViolationError: The standard for signed messages was
- blatantly violated.
- """
- try:
- return self._sAttachments
- except AttributeError:
- atts = super().attachments
-
- if len(atts) != 1:
- if self.errorBehavior & ErrorBehavior.STANDARDS_VIOLATION:
- if len(atts) == 0:
- logger.error('Signed message has no attachments, a violation of the standard.')
- self._sAttachments = None
- self._signedBody = None
- self._signedHtmlBody = None
- return
- # If there is at least one attachment, just try to use the
- # first.
- else:
- raise StandardViolationError('Signed messages without exactly 1 (regular) attachment constitue a violation of the standard.')
-
- # We need to unwrap the multipart stream.
- unwrapped = unwrapMultipart(atts[0].data)
-
- # Now store everything where it needs to be and make the
- # attachments.
- self._sAttachments = [self.__signedAttachmentClass(self, **att) for att in unwrapped['attachments']]
- self._signedBody = unwrapped['plain_body']
- self._signedHtmlBody = inputToBytes(unwrapped['html_body'], 'utf-8')
-
- return self._sAttachments
-
- @property
- def body(self) -> Optional[str]:
- """
- Returns the message body, if it exists.
- """
- try:
- return self._body
- except AttributeError:
- if self._ensureSet('_body', '__substg1.0_1000'):
- pass
- elif self.signedBody:
- self._body = self.signedBody
- else:
- # If the body doesn't exist, see if we can get it from the RTF
- # body.
- if self.deencapsulatedRtf and self.deencapsulatedRtf.content_type == 'text':
- self._body = self.deencapsulatedRtf.text
-
- if self._body:
- self._body = inputToString(self._body, 'utf-8')
- a = re.search('\n', self._body)
- if a is not None:
- if re.search('\r\n', self._body) is not None:
- self.__crlf = '\r\n'
- return self._body
-
- @property
- def htmlBody(self) -> Optional[bytes]:
- """
- Returns the html body, if it exists.
- """
- try:
- return self._htmlBody
- except AttributeError:
- if self._ensureSet('_htmlBody', '__substg1.0_10130102', False):
- # Reducing line repetition.
- pass
- elif self.signedHtmlBody:
- self._htmlBody = self.signedHtmlBody
- elif self.rtfBody:
- logger.info('HTML body was not found, attempting to generate from RTF.')
- if self.deencapsulatedRtf and self.deencapsulatedRtf.content_type == 'html':
- self._htmlBody = self.deencapsulatedRtf.html.encode('utf-8')
- else:
- logger.info('Could not deencapsulate HTML from RTF body.')
- elif self.body:
- # Convert the plain text body to html.
- logger.info('HTML body was not found, attempting to generate from plain text body.')
- correctedBody = html.escpae(self.body).replace('\r', '').replace('\n', '
')
- self._htmlBody = f'{correctedBody}'.encode('utf-8')
- else:
- logger.info('HTML body could not be found nor generated.')
-
- return self._htmlBody
-
- @property
- def _rawAttachments(self) -> List:
- """
- A property to allow access to the non-signed attachments.
- """
- return super().attachments
-
- @property
- def signedAttachmentClass(self):
- """
- The attachment class used for signed attachments.
- """
- return self.__signedAttachmentClass
-
- @property
- def signedBody(self) -> Optional[str]:
- """
- Returns the body from the signed message if it exists.
- """
- try:
- return self._signedBody
- except AttributeError:
- self.attachments
- return self._signedBody
-
- @property
- def signedHtmlBody(self) -> Optional[bytes]:
- """
- Returns the HTML body from the signed message if it exists.
- """
- try:
- return self._signedHtmlBody
- except AttributeError:
- self.attachments
- return self._signedHtmlBody
diff --git a/extract_msg/msg_classes/__init__.py b/extract_msg/msg_classes/__init__.py
new file mode 100644
index 00000000..e22dd0b4
--- /dev/null
+++ b/extract_msg/msg_classes/__init__.py
@@ -0,0 +1,47 @@
+"""
+Classes for opening an MSG file with.
+"""
+
+__all__ = [
+ # Classes:
+ 'AppointmentMeeting',
+ 'Calendar',
+ 'CalendarBase',
+ 'Contact',
+ 'MeetingCancellation',
+ 'MeetingException',
+ 'MeetingForwardNotification',
+ 'MeetingRelated',
+ 'MeetingRequest',
+ 'MeetingResponse',
+ 'Message',
+ 'MessageBase',
+ 'MessageSigned',
+ 'MessageSignedBase',
+ 'MSGFile',
+ 'Post',
+ 'StickyNote',
+ 'Task',
+ 'TaskRequest',
+]
+
+
+from .appointment import AppointmentMeeting
+from .calendar_base import CalendarBase
+from .calendar import Calendar
+from .contact import Contact
+from .meeting_cancellation import MeetingCancellation
+from .meeting_exception import MeetingException
+from .meeting_forward import MeetingForwardNotification
+from .meeting_related import MeetingRelated
+from .meeting_request import MeetingRequest
+from .meeting_response import MeetingResponse
+from .message import Message
+from .message_base import MessageBase
+from .message_signed import MessageSigned
+from .message_signed_base import MessageSignedBase
+from .msg import MSGFile
+from .post import Post
+from .sticky_note import StickyNote
+from .task import Task
+from .task_request import TaskRequest
\ No newline at end of file
diff --git a/extract_msg/appointment.py b/extract_msg/msg_classes/appointment.py
similarity index 79%
rename from extract_msg/appointment.py
rename to extract_msg/msg_classes/appointment.py
index 303e9b92..e205c3fd 100644
--- a/extract_msg/appointment.py
+++ b/extract_msg/msg_classes/appointment.py
@@ -4,13 +4,14 @@
import datetime
+import functools
from typing import Optional
-from . import constants
-from .enums import AppointmentStateFlag, RecurPatternType, ResponseStatus
+from ..constants import HEADER_FORMAT_TYPE, ps
from .calendar import Calendar
-from .structures.entry_id import EntryID
+from ..enums import AppointmentStateFlag, RecurPatternType, ResponseStatus
+from ..structures.entry_id import EntryID
class AppointmentMeeting(Calendar):
@@ -22,53 +23,53 @@ class AppointmentMeeting(Calendar):
object.
"""
- @property
+ @functools.cached_property
def appointmentCounterProposal(self) -> bool:
"""
Indicates to the organizer that there are counter proposals that have
not been accepted or rejected by the organizer.
"""
- return self._ensureSetNamed('_appointmentCounterProposal', '8257', constants.PSETID_APPOINTMENT, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8257', ps.PSETID_APPOINTMENT, bool, False)
- @property
+ @functools.cached_property
def appointmentLastSequence(self) -> Optional[int]:
"""
The last sequence number that was sent to any attendee.
"""
- return self._ensureSetNamed('_appointmentLastSequence', '8203', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8203', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def appointmentProposalNumber(self) -> Optional[int]:
"""
The number of attendees who have sent counter propostals that have not
been accepted or rejected by the organizer.
"""
- return self._ensureSetNamed('_appointmentProposalNumber', '8259', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8259', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def appointmentReplyName(self) -> Optional[datetime.datetime]:
"""
The user who last replied to the meeting request or meeting update.
"""
- return self._ensureSetNamed('_appointmentReplyName', '8230', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8230', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def appointmentReplyTime(self) -> Optional[datetime.datetime]:
"""
The date and time at which the attendee responded to a received Meeting
Request object of Meeting Update object in UTC.
"""
- return self._ensureSetNamed('_appointmentReplyTime', '8220', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8220', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def appointmentSequenceTime(self) -> Optional[datetime.datetime]:
"""
The date and time at which the appointmentSequence property was last
modified.
"""
- return self._ensureSetNamed('_appointmentSequenceTime', '8202', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8202', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def autoFillLocation(self) -> bool:
"""
A value of True indicates that the value of the location property is set
@@ -78,17 +79,17 @@ def autoFillLocation(self) -> bool:
A value of False indicates that the value of the location property is
not automatically set.
"""
- return self._ensureSetNamed('_autoFillLocation', '823A', constants.PSETID_APPOINTMENT, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('823A', ps.PSETID_APPOINTMENT, bool, False)
- @property
+ @functools.cached_property
def fInvited(self) -> bool:
"""
Whether a Meeting Request object has been sent out.
"""
- return self._ensureSetNamed('_fInvited', '8229', constants.PSETID_APPOINTMENT, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8229', ps.PSETID_APPOINTMENT, bool, False)
@property
- def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
+ def headerFormatProperties(self) -> HEADER_FORMAT_TYPE:
"""
Returns a dictionary of properties, in order, to be formatted into the
header. Keys are the names to use in the header while the values are one
@@ -161,7 +162,7 @@ def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
},
}
- @property
+ @functools.cached_property
def isMeeting(self) -> bool:
"""
Attempts to determine if the object is a Meeting. True if meeting, False
@@ -169,9 +170,9 @@ def isMeeting(self) -> bool:
"""
return self.appointmentStateFlags and AppointmentStateFlag.MEETING in self.appointmentStateFlags
- @property
+ @functools.cached_property
def originalStoreEntryID(self) -> Optional[EntryID]:
"""
The EntryID of the delegator's message store.
"""
- return self._ensureSetNamed('_originalStoreEntryID', '8237', constants.PSETID_APPOINTMENT, overrideClass = EntryID.autoCreate)
+ return self._getNamedAs('8237', ps.PSETID_APPOINTMENT, EntryID.autoCreate)
diff --git a/extract_msg/calendar.py b/extract_msg/msg_classes/calendar.py
similarity index 59%
rename from extract_msg/calendar.py
rename to extract_msg/msg_classes/calendar.py
index b73f796e..c97144e0 100644
--- a/extract_msg/calendar.py
+++ b/extract_msg/msg_classes/calendar.py
@@ -4,12 +4,13 @@
import datetime
+import functools
-from typing import Optional, Set
+from typing import Optional
-from . import constants
+from ..constants import ps
from .calendar_base import CalendarBase
-from .enums import ClientIntentFlag
+from ..enums import ClientIntentFlag
class Calendar(CalendarBase):
@@ -17,14 +18,14 @@ class Calendar(CalendarBase):
A calendar object.
"""
- @property
- def clientIntent(self) -> Optional[Set[ClientIntentFlag]]:
+ @functools.cached_property
+ def clientIntent(self) -> Optional[ClientIntentFlag]:
"""
A set of the actions a user has taken on a Meeting object.
"""
- return self._ensureSetNamed('_clientIntent', '0015', constants.PSETID_CALENDAR_ASSISTANT, overrideClass = ClientIntentFlag.fromBits)
+ return self._getNamedAs('0015', ps.PSETID_CALENDAR_ASSISTANT, ClientIntentFlag)
- @property
+ @functools.cached_property
def fExceptionalAttendees(self) -> Optional[bool]:
"""
Indicates that it is a Recurring Calendar object with one or more
@@ -34,58 +35,58 @@ def fExceptionalAttendees(self) -> Optional[bool]:
SHOULD NOT be set for any Calendar object other than that of the
organizer's.
"""
- return self._ensureSetNamed('_fExceptionalAttendees', '822B', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('822B', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def reminderDelta(self) -> Optional[int]:
"""
The interval, in minutes, between the time at which the reminder first
becomes overdue and the start time of the Calendar object.
"""
- return self._ensureSetNamed('_reminderDelta', '8501', constants.PSETID_COMMON)
+ return self._getNamedAs('8501', ps.PSETID_COMMON)
- @property
+ @functools.cached_property
def reminderFileParameter(self) -> Optional[str]:
"""
The full path (MAY only specify the file name) of the sound that a
client SHOULD play when the reminder for the Message Object becomes
overdue.
"""
- return self._ensureSetNamed('_reminderFileParameter', '851F', constants.PSETID_COMMON)
+ return self._getNamedAs('851F', ps.PSETID_COMMON)
- @property
+ @functools.cached_property
def reminderOverride(self) -> bool:
"""
Specifies if clients SHOULD respect the value of the reminderPlaySound
property and the reminderFileParameter property.
"""
- return self._ensureSetNamed('_reminderOverride', '851C', constants.PSETID_COMMON, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('851C', ps.PSETID_COMMON, bool, False)
- @property
+ @functools.cached_property
def reminderPlaySound(self) -> bool:
"""
Specified that the cliebnt should play a sound when the reminder becomes
overdue.
"""
- return self._ensureSetNamed('_reminderPlaySound', '851E', constants.PSETID_COMMON, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('851E', ps.PSETID_COMMON, bool, False)
- @property
+ @functools.cached_property
def reminderSet(self) -> bool:
"""
Specifies whether a reminder is set on the object.
"""
- return self._ensureSetNamed('_reminderSet', '8503', constants.PSETID_COMMON, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8503', ps.PSETID_COMMON, bool, False)
- @property
+ @functools.cached_property
def reminderSignalTime(self) -> Optional[datetime.datetime]:
"""
The point in time when a reminder transitions from pending to overdue.
"""
- return self._ensureSetNamed('_reminderSignalTime', '8560', constants.PSETID_COMMON)
+ return self._getNamedAs('8560', ps.PSETID_COMMON)
- @property
+ @functools.cached_property
def reminderTime(self) -> Optional[datetime.datetime]:
"""
The time after which the user would be late.
"""
- return self._ensureSetNamed('_reminderTime', '8502', constants.PSETID_COMMON)
+ return self._getNamedAs('8502', ps.PSETID_COMMON)
diff --git a/extract_msg/calendar_base.py b/extract_msg/msg_classes/calendar_base.py
similarity index 53%
rename from extract_msg/calendar_base.py
rename to extract_msg/msg_classes/calendar_base.py
index 79b89e29..73e1ed75 100644
--- a/extract_msg/calendar_base.py
+++ b/extract_msg/msg_classes/calendar_base.py
@@ -4,18 +4,19 @@
import datetime
+import functools
import logging
-from typing import List, Optional, Set, Tuple, Union
+from typing import List, Optional, Union
-from . import constants
-from .enums import AppointmentAuxilaryFlag, AppointmentColor, AppointmentStateFlag, BusyStatus, IconIndex, MeetingRecipientType, ResponseStatus
+from ..constants import ps
+from ..enums import AppointmentAuxilaryFlag, AppointmentColor, AppointmentStateFlag, BusyStatus, IconIndex, MeetingRecipientType, ResponseStatus
from .message_base import MessageBase
-from .structures.entry_id import EntryID
-from .structures.misc_id import GlobalObjectID
-from .structures.recurrence_pattern import RecurrencePattern
-from .structures.time_zone_definition import TimeZoneDefinition
-from .structures.time_zone_struct import TimeZoneStruct
+from ..structures.entry_id import EntryID
+from ..structures.misc_id import GlobalObjectID
+from ..structures.recurrence_pattern import RecurrencePattern
+from ..structures.time_zone_definition import TimeZoneDefinition
+from ..structures.time_zone_struct import TimeZoneStruct
logger = logging.getLogger(__name__)
@@ -31,154 +32,147 @@ def _genRecipient(self, recipientType, recipientInt : MeetingRecipientType) -> O
"""
Returns the specified recipient field.
"""
- private = '_' + recipientType
recipientInt = MeetingRecipientType(recipientInt)
- try:
- return getattr(self, private)
- except AttributeError:
- value = None
- # Check header first.
- if self.headerInit():
- value = self.header[recipientType]
- if value:
- value = value.replace(',', self.recipientSeparator)
-
- # If the header had a blank field or didn't have the field, generate
- # it manually.
- if not value:
- # Check if the header has initialized.
- if self.headerInit():
- logger.info(f'Header found, but "{recipientType}" is not included. Will be generated from other streams.')
-
- # Get a list of the recipients of the specified type.
- foundRecipients = tuple(recipient.formatted for recipient in self.recipients if recipient.type == recipientInt)
-
- # If we found recipients, join them with the recipient separator
- # and a space.
- if len(foundRecipients) > 0:
- value = (self.recipientSeparator + ' ').join(foundRecipients)
-
- # Code to fix the formatting so it's all a single line. This allows
- # the user to format it themself if they want. This should probably
- # be redone to use re or something, but I can do that later. This
- # shouldn't be a huge problem for now.
+ value = None
+ # Check header first.
+ if self.headerInit():
+ value = self.header[recipientType]
if value:
- value = value.replace(' \r\n\t', ' ').replace('\r\n\t ', ' ').replace('\r\n\t', ' ')
- value = value.replace('\r\n', ' ').replace('\r', ' ').replace('\n', ' ')
- while value.find(' ') != -1:
- value = value.replace(' ', ' ')
+ value = value.replace(',', self.recipientSeparator)
+
+ # If the header had a blank field or didn't have the field, generate
+ # it manually.
+ if not value:
+ # Check if the header has initialized.
+ if self.headerInit():
+ logger.info(f'Header found, but "{recipientType}" is not included. Will be generated from other streams.')
- # Set the field in the class.
- setattr(self, private, value)
+ # Get a list of the recipients of the specified type.
+ foundRecipients = tuple(recipient.formatted for recipient in self.recipients if recipient.type == recipientInt)
- return value
+ # If we found recipients, join them with the recipient separator
+ # and a space.
+ if len(foundRecipients) > 0:
+ value = (self.recipientSeparator + ' ').join(foundRecipients)
- @property
+ # Code to fix the formatting so it's all a single line. This allows
+ # the user to format it themself if they want. This should probably
+ # be redone to use re or something, but I can do that later. This
+ # shouldn't be a huge problem for now.
+ if value:
+ value = value.replace(' \r\n\t', ' ').replace('\r\n\t ', ' ').replace('\r\n\t', ' ')
+ value = value.replace('\r\n', ' ').replace('\r', ' ').replace('\n', ' ')
+ while value.find(' ') != -1:
+ value = value.replace(' ', ' ')
+
+ return value
+
+ @functools.cached_property
def allAttendeesString(self) -> Optional[str]:
"""
A list of all attendees, excluding the organizer.
"""
- return self._ensureSetNamed('_allAttendeesString', '8238', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8238', ps.PSETID_APPOINTMENT)
- @property
- def appointmentAuxilaryFlags(self) -> Optional[Set[AppointmentAuxilaryFlag]]:
+ @functools.cached_property
+ def appointmentAuxilaryFlags(self) -> Optional[AppointmentAuxilaryFlag]:
"""
The auxiliary state of the object.
"""
- return self._ensureSetNamed('_appointmentAuxilaryFlags', '8207', constants.PSETID_APPOINTMENT, overrideClass = AppointmentAuxilaryFlag.fromBits)
+ return self._getNamedAs('8207', ps.PSETID_APPOINTMENT, AppointmentAuxilaryFlag)
- @property
+ @functools.cached_property
def appointmentColor(self) -> Optional[AppointmentColor]:
"""
The color to be used when displaying a Calendar object.
"""
- return self._ensureSetNamed('_appointmentColor', '8214', constants.PSETID_APPOINTMENT, overrideClass = AppointmentColor)
+ return self._getNamedAs('8214', ps.PSETID_APPOINTMENT, AppointmentColor)
- @property
+ @functools.cached_property
def appointmentDuration(self) -> Optional[int]:
"""
The length of the event, in minutes.
"""
- return self._ensureSetNamed('_appointmentDuration', '8213', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8213', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def appointmentEndWhole(self) -> Optional[datetime.datetime]:
"""
The end date and time of the event in UTC.
"""
- return self._ensureSetNamed('_appointmentEndWhole', '820E', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('820E', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def appointmentNotAllowPropose(self) -> bool:
"""
Indicates that attendees are not allowed to propose a new date and/or
time for the meeting if True.
"""
- return self._ensureSetNamed('_appointmentNotAllowPropose', '8259', constants.PSETID_APPOINTMENT, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8259', ps.PSETID_APPOINTMENT, bool, False)
- @property
+ @functools.cached_property
def appointmentRecur(self) -> Optional[RecurrencePattern]:
"""
Specifies the dates and times when a recurring series occurs by using
one of the recurrence patterns and ranges specified in this section.
"""
- return self._ensureSetNamed('_appointmentRecur', '8216', constants.PSETID_APPOINTMENT, overrideClass = RecurrencePattern)
+ return self._getNamedAs('8216', ps.PSETID_APPOINTMENT, RecurrencePattern)
- @property
+ @functools.cached_property
def appointmentSequence(self) -> Optional[int]:
"""
Specified the sequence number of a Meeting object. A meeting object
begins with the sequence number set to 0 and is incremented each time
the organizer sends out a Meeting Update object.
"""
- return self._ensureSetNamed('_appointmentSequence', '8201', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8201', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def appointmentStartWhole(self) -> Optional[datetime.datetime]:
"""
The start date and time of the event in UTC.
"""
- return self._ensureSetNamed('_appointmentStartWhole', '820D', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('820D', ps.PSETID_APPOINTMENT)
- @property
- def appointmentStateFlags(self) -> Optional[Set[AppointmentStateFlag]]:
+ @functools.cached_property
+ def appointmentStateFlags(self) -> Optional[AppointmentStateFlag]:
"""
The appointment state of the object.
"""
- return self._ensureSetNamed('_appointmentStateFlags', '8217', constants.PSETID_APPOINTMENT, overrideClass = AppointmentStateFlag.fromBits)
+ return self._getNamedAs('8217', ps.PSETID_APPOINTMENT, AppointmentStateFlag)
- @property
+ @functools.cached_property
def appointmentSubType(self) -> bool:
"""
Whether the event is an all-day event or not.
"""
- return self._ensureSetNamed('_appointmentSubType', '8215', constants.PSETID_APPOINTMENT, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8215', ps.PSETID_APPOINTMENT, bool, False)
- @property
+ @functools.cached_property
def appointmentTimeZoneDefinitionEndDisplay(self) -> Optional[TimeZoneDefinition]:
"""
Specifies the time zone information for the appointmentEndWhole property
Used to convert the end date and time to and from UTC.
"""
- return self._ensureSetNamed('_appointmentTimeZoneDefinitionEndDisplay', '825F', constants.PSETID_APPOINTMENT, overrideClass = TimeZoneDefinition)
+ return self._getNamedAs('825F', ps.PSETID_APPOINTMENT, TimeZoneDefinition)
- @property
+ @functools.cached_property
def appointmentTimeZoneDefinitionRecur(self) -> Optional[TimeZoneDefinition]:
"""
Specified the time zone information that specifies how to convert the
meeting date and time on a recurring series to and from UTC.
"""
- return self._ensureSetNamed('_appointmentTimeZoneDefinitionRecur', '8260', constants.PSETID_APPOINTMENT, overrideClass = TimeZoneDefinition)
+ return self._getNamedAs('8260', ps.PSETID_APPOINTMENT, TimeZoneDefinition)
- @property
+ @functools.cached_property
def appointmentTimeZoneDefinitionStartDisplay(self) -> Optional[TimeZoneDefinition]:
"""
Specifies the time zone information for the appointmentStartWhole
property. Used to convert the start date and time to and from UTC.
"""
- return self._ensureSetNamed('_appointmentTimeZoneDefinitionStartDisplay', '825E', constants.PSETID_APPOINTMENT, overrideClass = TimeZoneDefinition)
+ return self._getNamedAs('825E', ps.PSETID_APPOINTMENT, TimeZoneDefinition)
- @property
+ @functools.cached_property
def appointmentUnsendableRecipients(self) -> Optional[bytes]:
"""
A list of unsendable attendees.
@@ -187,69 +181,69 @@ def appointmentUnsendableRecipients(self) -> Optional[bytes]:
the specifications. If you have examples, let me know and I can ask you
to run a verification on it.
"""
- return self._ensureSetNamed('_appointmentUnsendableRecipients', '825D', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('825D', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def bcc(self) -> Optional[str]:
"""
Returns the bcc field, if it exists.
"""
return self._genRecipient('bcc', MeetingRecipientType.SENDABLE_RESOURCE_OBJECT)
- @property
+ @functools.cached_property
def birthdayContactAttributionDisplayName(self) -> Optional[str]:
"""
Indicated the name of the contact associated with the birthday event.
"""
- return self._ensureSetNamed('_birthdayContactAttributionDisplayName', 'BirthdayContactAttributionDisplayName', constants.PSETID_ADDRESS)
+ return self._getNamedAs('BirthdayContactAttributionDisplayName', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def birthdayContactEntryID(self) -> Optional[EntryID]:
"""
Indicates the EntryID of the contact associated with the birthday event.
"""
- return self._ensureSetNamed('_birthdayContactEntryID', 'BirthdayContactEntryId', constants.PSETID_ADDRESS, overrideClass = EntryID.autoCreate)
+ return self._getNamedAs('BirthdayContactEntryId', ps.PSETID_ADDRESS, EntryID.autoCreate)
- @property
+ @functools.cached_property
def birthdayContactPersonGuid(self) -> Optional[bytes]:
"""
Indicates the person ID's GUID of the contact associated with the
birthday event.
"""
- return self._ensureSetNamed('_birthdayContactPersonGuid', 'BirthdayContactPersonGuid', constants.PSETID_ADDRESS)
+ return self._getNamedAs('BirthdayContactPersonGuid', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def busyStatus(self) -> Optional[BusyStatus]:
"""
Specified the availability of a user for the event described by the
object.
"""
- return self._ensureSetNamed('_busyStatus', '8205', constants.PSETID_APPOINTMENT, overrideClass = BusyStatus)
+ return self._getNamedAs('8205', ps.PSETID_APPOINTMENT, BusyStatus)
- @property
+ @functools.cached_property
def cc(self) -> Optional[str]:
"""
Returns the cc field, if it exists.
"""
return self._genRecipient('cc', MeetingRecipientType.SENDABLE_OPTIONAL_ATTENDEE)
- @property
+ @functools.cached_property
def ccAttendeesString(self) -> Optional[str]:
"""
A list of all the sendable attendees, who are also optional attendees.
"""
- return self._ensureSetNamed('_ccAttendeesString', '823C', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('823C', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def cleanGlobalObjectID(self) -> Optional[GlobalObjectID]:
"""
The value of the globalObjectID property for an object that represents
an Exception object to a recurring series, where the year, month, and
day fields are all 0.
"""
- return self._ensureSetNamed('_cleanGlobalObjectID', '0023', constants.PSETID_MEETING, overrideClass = GlobalObjectID)
+ return self._getNamedAs('0023', ps.PSETID_MEETING, GlobalObjectID)
- @property
+ @functools.cached_property
def clipEnd(self) -> Optional[datetime.datetime]:
"""
For single-instance Calendar objects, the end date and time of the
@@ -260,9 +254,9 @@ def clipEnd(self) -> Optional[datetime.datetime]:
Honestly, not sure what this is. [MS-OXOCAL]: PidLidClipEnd.
"""
- return self._ensureSetNamed('_clipEnd', '8236', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8236', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def clipStart(self) -> Optional[datetime.datetime]:
"""
For single-instance Calendar objects, the start date and time of the
@@ -271,260 +265,260 @@ def clipStart(self) -> Optional[datetime.datetime]:
Honestly, not sure what this is. [MS-OXOCAL]: PidLidClipStart.
"""
- return self._ensureSetNamed('_clipStart', '8235', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8235', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def commonEnd(self) -> Optional[datetime.datetime]:
"""
The end date and time of an event. MUST be equal to appointmentEndWhole.
"""
- return self._ensureSetNamed('_commonEnd', '8517', constants.PSETID_COMMON)
+ return self._getNamedAs('8517', ps.PSETID_COMMON)
- @property
+ @functools.cached_property
def commonStart(self) -> Optional[datetime.datetime]:
"""
The start date and time of an event. MUST be equal to
appointmentStartWhole.
"""
- return self._ensureSetNamed('_commonStart', '8516', constants.PSETID_COMMON)
+ return self._getNamedAs('8516', ps.PSETID_COMMON)
- @property
+ @functools.cached_property
def endDate(self) -> Optional[datetime.datetime]:
"""
The end date of the appointment.
"""
- return self._ensureSetProperty('_endDate', '00610040')
+ return self._getPropertyAs('00610040')
- @property
+ @functools.cached_property
def globalObjectID(self) -> Optional[GlobalObjectID]:
"""
The unique identifier or the Calendar object.
"""
- return self._ensureSetNamed('_globalObjectID', '0003', constants.PSETID_MEETING, overrideClass = GlobalObjectID)
+ return self._getNamedAs('0003', ps.PSETID_MEETING, GlobalObjectID)
- @property
+ @functools.cached_property
def iconIndex(self) -> Optional[Union[IconIndex, int]]:
"""
The icon to use for the object.
"""
- return self._ensureSetProperty('_iconIndex', '10800003', overrideClass = IconIndex.tryMake)
+ return self._getPropertyAs('10800003', IconIndex.tryMake)
- @property
+ @functools.cached_property
def isBirthdayContactWritable(self) -> bool:
"""
Indicates whether the contact associated with the birthday event is
writable.
"""
- return self._ensureSetNamed('_isBirthdayContactWritable', 'IsBirthdayContactWritable', constants.PSETID_ADDRESS, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('IsBirthdayContactWritable', ps.PSETID_ADDRESS, bool, False)
- @property
+ @functools.cached_property
def isException(self) -> bool:
"""
Whether the object represents an exception. False indicates that the
object represents a recurring series or a single-instance object.
"""
- return self._ensureSetNamed('_isException', '000A', constants.PSETID_MEETING, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('000A', ps.PSETID_MEETING, bool, False)
- @property
+ @functools.cached_property
def isRecurring(self) -> bool:
"""
Whether the object is associated with a recurring series.
"""
- return self._ensureSetNamed('_isRecurring', '0005', constants.PSETID_MEETING, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('0005', ps.PSETID_MEETING, bool, False)
- @property
+ @functools.cached_property
def keywords(self) -> Optional[List[str]]:
"""
The color to be used when displaying a Calendar object.
"""
- return self._ensureSet('_keywords', 'Keywords')
+ return self._getNamedAs('Keywords', ps.PS_PUBLIC_STRINGS)
- @property
- def linkedTaskItems(self) -> Optional[Tuple[EntryID]]:
+ @functools.cached_property
+ def linkedTaskItems(self) -> Optional[List[EntryID]]:
"""
A list of PidTagEntryId properties of Task objects related to the
Calendar object that are set by a client.
"""
- return self._ensureSetNamed('_linkedTaskItems', '820C', constants.PSETID_APPOINTMENT, overrideClass = lambda x : tuple(EntryID.autoCreate(y) for y in x))
+ return self._getNamedAs('820C', ps.PSETID_APPOINTMENT, lambda x : list(EntryID.autoCreate(y) for y in x))
- @property
+ @functools.cached_property
def location(self) -> Optional[str]:
"""
Returns the location of the meeting.
"""
- return self._ensureSetNamed('_location', '8208', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8208', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def meetingDoNotForward(self) -> bool:
"""
Whether to allow the meeting to be forwarded. True disallows forwarding.
"""
- return self._ensureSetNamed('_meetingDoNotForward', 'DoNotForward', constants.PS_PUBLIC_STRINGS, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('DoNotForward', ps.PS_PUBLIC_STRINGS, bool, False)
- @property
+ @functools.cached_property
def meetingWorkspaceUrl(self) -> Optional[str]:
"""
The URL of the Meeting Workspace, as specified in [MS-MEETS], that is
associated with a Calendar object.
"""
- return self._ensureSetNamed('_meetingWorkspaceUrl', '8209', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8209', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def nonSendableBcc(self) -> Optional[str]:
"""
A list of all unsendable attendees who are also resource objects.
"""
- return self._ensureSetNamed('_nonSendableBcc', '8538', constants.PSETID_COMMON)
+ return self._getNamedAs('8538', ps.PSETID_COMMON)
- @property
+ @functools.cached_property
def nonSendableCc(self) -> Optional[str]:
"""
A list of all unsendable attendees who are also optional attendees.
"""
- return self._ensureSetNamed('_nonSendableCc', '8537', constants.PSETID_COMMON)
+ return self._getNamedAs('8537', ps.PSETID_COMMON)
- @property
+ @functools.cached_property
def nonSendableTo(self) -> Optional[str]:
"""
A list of all unsendable attendees who are also required attendees.
"""
- return self._ensureSetNamed('_nonSendableTo', '8536', constants.PSETID_COMMON)
+ return self._getNamedAs('8536', ps.PSETID_COMMON)
- @property
+ @functools.cached_property
def nonSendBccTrackStatus(self) -> Optional[List[ResponseStatus]]:
"""
A ResponseStatus for each of the attendees in nonSendableBcc.
"""
- return self._ensureSetNamed('_nonSendBccTrackStatus', '8545', constants.PSETID_COMMON, overrideClass = (lambda x : (ResponseStatus(y) for y in x)))
+ return self._getNamedAs('8545', ps.PSETID_COMMON, lambda x : list(ResponseStatus(y) for y in x))
- @property
+ @functools.cached_property
def nonSendCcTrackStatus(self) -> Optional[List[ResponseStatus]]:
"""
A ResponseStatus for each of the attendees in nonSendableCc.
"""
- return self._ensureSetNamed('_nonSendCcTrackStatus', '8544', constants.PSETID_COMMON, overrideClass = (lambda x : (ResponseStatus(y) for y in x)))
+ return self._getNamedAs('8544', ps.PSETID_COMMON, lambda x : list(ResponseStatus(y) for y in x))
- @property
+ @functools.cached_property
def nonSendToTrackStatus(self) -> Optional[List[ResponseStatus]]:
"""
A ResponseStatus for each of the attendees in nonSendableTo.
"""
- return self._ensureSetNamed('_nonSendToTrackStatus', '8543', constants.PSETID_COMMON, overrideClass = (lambda x : (ResponseStatus(y) for y in x)))
+ return self._getNamedAs('8543', ps.PSETID_COMMON, lambda x : list(ResponseStatus(y) for y in x))
- @property
+ @functools.cached_property
def optionalAttendees(self) -> Optional[str]:
"""
Returns the optional attendees of the meeting.
"""
- return self._ensureSetNamed('_optionalAttendees', '0007', constants.PSETID_MEETING)
+ return self._getNamedAs('0007', ps.PSETID_MEETING)
@property
def organizer(self) -> Optional[str]:
"""
The meeting organizer.
"""
- return self._ensureSet('_organizer', '__substg1.0_0042')
+ return self._getStringStream('__substg1.0_0042')
- @property
+ @functools.cached_property
def ownerAppointmentID(self) -> Optional[int]:
"""
A quasi-unique value amond all Calendar objects in a user's mailbox.
Assists a client or server in finding a Calendar object but is not
guarenteed to be unique amoung all objects.
"""
- return self._ensureSetProperty('_ownerAppointmentID', '00620003')
+ return self._getPropertyAs('00620003')
- @property
+ @functools.cached_property
def ownerCriticalChange(self) -> Optional[datetime.datetime]:
"""
The date and time at which a Meeting Request object was sent by the
organizer, in UTC.
"""
- return self._ensureSetNamed('_ownerCriticalChange', '001A', constants.PSETID_MEETING)
+ return self._getNamedAs('001A', ps.PSETID_MEETING)
- @property
+ @functools.cached_property
def recurrencePattern(self) -> Optional[str]:
"""
A description of the recurrence specified by the appointmentRecur
property.
"""
- return self._ensureSetNamed('_recurrencePattern', '8232', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8232', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def recurring(self) -> bool:
"""
Specifies whether the object represents a recurring series.
"""
- return self._ensureSetNamed('_recurring', '8223', constants.PSETID_APPOINTMENT, overrideClass = bool, preserveNone = True)
+ return self._getNamedAs('8223', ps.PSETID_APPOINTMENT, bool, True)
- @property
+ @functools.cached_property
def replyRequested(self) -> bool:
"""
Whether the organizer requests a reply from attendees.
"""
- return self._ensureSetProperty('_replyRequested', '0C17000B', overrideClass = bool, preserveNone = False)
+ return self._getPropertyAs('0C17000B', bool, False)
- @property
+ @functools.cached_property
def requiredAttendees(self) -> Optional[str]:
"""
Returns the required attendees of the meeting.
"""
- return self._ensureSetNamed('_requiredAttendees', '0006', constants.PSETID_MEETING)
+ return self._getNamedAs('0006', ps.PSETID_MEETING)
- @property
+ @functools.cached_property
def resourceAttendees(self) -> Optional[str]:
"""
Returns the resource attendees of the meeting.
"""
- return self._ensureSetNamed('_resourceAttendees', '0008', constants.PSETID_MEETING)
+ return self._getNamedAs('0008', ps.PSETID_MEETING)
- @property
+ @functools.cached_property
def responseRequested(self) -> bool:
"""
Whether to send Meeting Response objects to the organizer.
"""
- return self._ensureSetProperty('_responseRequested', '0063000B', overrideClass = bool, preserveNone = False)
+ return self._getPropertyAs('0063000B', bool, False)
- @property
+ @functools.cached_property
def responseStatus(self) -> ResponseStatus:
"""
The response status of an attendee.
"""
- return self._ensureSetNamed('_responseStatus', '8218', constants.PSETID_APPOINTMENT, overrideClass = lambda x: ResponseStatus(x or 0), preserveNone = False)
+ return self._getNamedAs('8218', ps.PSETID_APPOINTMENT, lambda x: ResponseStatus(x or 0), False)
- @property
+ @functools.cached_property
def startDate(self) -> Optional[datetime.datetime]:
"""
The start date of the appointment.
"""
- return self._ensureSetProperty('_startDate', '00600040')
+ return self._getPropertyAs('00600040')
- @property
+ @functools.cached_property
def timeZoneDescription(self) -> Optional[str]:
"""
A human-readable description of the time zone that is represented by the
data in the timeZoneStruct property.
"""
- return self._ensureSetNamed('_timeZoneDescription', '8234', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8234', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def timeZoneStruct(self) -> Optional[TimeZoneStruct]:
"""
Set on a recurring series to specify time zone information. Specifies
how to convert time fields between local time and UTC.
"""
- return self._ensureSetNamed('_timeZoneStruct', '8233', constants.PSETID_APPOINTMENT, overrideClass = TimeZoneStruct)
+ return self._getNamedAs('8233', ps.PSETID_APPOINTMENT, TimeZoneStruct)
- @property
+ @functools.cached_property
def to(self) -> Optional[str]:
"""
Returns the to field, if it exists.
"""
return self._genRecipient('to', MeetingRecipientType.SENDABLE_REQUIRED_ATTENDEE)
- @property
+ @functools.cached_property
def toAttendeesString(self) -> Optional[str]:
"""
A list of all the sendable attendees, who are also required attendees.
"""
- return self._ensureSetNamed('_toAttendeesString', '823B', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('823B', ps.PSETID_APPOINTMENT)
diff --git a/extract_msg/contact.py b/extract_msg/msg_classes/contact.py
similarity index 57%
rename from extract_msg/contact.py
rename to extract_msg/msg_classes/contact.py
index 740ab8e1..d9cc40c1 100644
--- a/extract_msg/contact.py
+++ b/extract_msg/msg_classes/contact.py
@@ -3,23 +3,21 @@
]
+import io
import datetime
+import functools
-from typing import Dict, List, Optional, Set, Tuple, TYPE_CHECKING,Union
+from typing import Dict, List, Optional, Set, Tuple, Union
-from . import constants
-from .enums import ContactLinkState, ElectronicAddressProperties, Gender, PostalAddressID
+from ..constants import HEADER_FORMAT_TYPE, ps
+from ..enums import (
+ ContactLinkState, ElectronicAddressProperties, Gender,
+ InsecureFeatures, PostalAddressID
+ )
+from ..exceptions import SecurityError
from .message_base import MessageBase
-from .structures.entry_id import EntryID
-from .structures.business_card import BusinessCardDisplayDefinition
-
-
-# Allow for type checking an optional dependency.
-if TYPE_CHECKING:
- try:
- import PIL.Image
- except ImportError:
- pass
+from ..structures.entry_id import EntryID
+from ..structures.business_card import BusinessCardDisplayDefinition
class Contact(MessageBase):
@@ -27,109 +25,85 @@ class Contact(MessageBase):
Class used for parsing contacts.
"""
- def __init__(self, path, **kwargs):
- """
- :param path: path to the msg file in the system or is the raw msg file.
- :param prefix: used for extracting embeded msg files inside the main
- one. Do not set manually unless you know what you are doing.
- :param parentMsg: Used for synchronizing named properties instances. Do
- not set this unless you know what you are doing.
- :param attachmentClass: optional, the class the MSGFile object will use
- for attachments. You probably should not change this value unless
- you know what you are doing.
- :param delayAttachments: optional, delays the initialization of
- attachments until the user attempts to retrieve them. Allows MSG
- files with bad attachments to be initialized so the other data can
- be retrieved.
- :param filename: optional, the filename to be used by default when
- saving.
- :param attachmentErrorBehavior: Optional, the behavior to use in the
- event of an error when parsing the attachments.
- :param overrideEncoding: optional, an encoding to use instead of the one
- specified by the msg file. Do not report encoding errors caused by
- this.
- """
- super().__init__(path, **kwargs)
- self.named
- self.namedProperties
-
- @property
+ @functools.cached_property
def account(self) -> Optional[str]:
"""
The account name of the contact.
"""
- return self._ensureSet('_account', '__substg1.0_3A00')
+ return self._getStringStream('__substg1.0_3A00')
- @property
- def addressBookProviderArrayType(self) -> Optional[Set[ElectronicAddressProperties]]:
+ @functools.cached_property
+ def addressBookProviderArrayType(self) -> Optional[ElectronicAddressProperties]:
"""
- A set of which Electronic Address properties are set on the contact.
+ A union of which Electronic Address properties are set on the contact.
Property is stored in the MSG file as a sinlge int. The result should be
identical to addressBookProviderEmailList.
"""
- return self._ensureSetNamed('_addressBookProviderArrayType', '8029', constants.PSETID_ADDRESS, ElectronicAddressProperties.fromBits)
+ return self._getNamedAs('8029', ps.PSETID_ADDRESS, ElectronicAddressProperties)
- @property
+ @functools.cached_property
def addressBookProviderEmailList(self) -> Optional[Set[ElectronicAddressProperties]]:
"""
A set of which Electronic Address properties are set on the contact.
"""
- return self._ensureSetNamed('_addressBookProviderEmailList', '8028', constants.PSETID_ADDRESS, overrideClass = lambda x : {ElectronicAddressProperties(y) for y in x})
+ return self._getNamedAs('8028', ps.PSETID_ADDRESS, lambda x : {ElectronicAddressProperties(y) for y in x})
- @property
+ @functools.cached_property
def assistant(self) -> Optional[str]:
"""
The name of the contact's assistant.
"""
- return self._ensureSet('_assistant', '__substg1.0_3A30')
+ return self._getStringStream('__substg1.0_3A30')
- @property
+ @functools.cached_property
def assistantTelephoneNumber(self) -> Optional[str]:
"""
Contains the telephone number of the contact's administrative assistant.
"""
- return self._ensureSet('_assistantTelephoneNumber', '__substg1.0_3A2E')
+ return self._getStringStream('__substg1.0_3A2E')
- @property
+ @functools.cached_property
def autoLog(self) -> bool:
"""
Whether the client should create a Journal object for each action
associated with the Contact object.
"""
- return self._ensureSetNamed('_autoLog', '8025', constants.PSETID_ADDRESS, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8025', ps.PSETID_ADDRESS, bool, False)
- @property
+ @functools.cached_property
def billing(self) -> Optional[str]:
"""
Billing information for the contact.
"""
- return self._ensureSetNamed('_billing', '8535', constants.PSETID_COMMON)
+ return self._getNamedAs('8535', ps.PSETID_COMMON)
- @property
+ @functools.cached_property
def birthday(self) -> Optional[datetime.datetime]:
"""
The birthday of the contact at 11:59 UTC.
"""
- return self._ensureSetProperty('_birthday', '3A420040')
+ return self._getPropertyAs('3A420040')
- @property
+ @functools.cached_property
def birthdayEventEntryID(self) -> Optional[EntryID]:
"""
The EntryID of an optional Appointement object that represents the
contact's birtday.
"""
- return self._ensureSetNamed('_birthdayEventEntryID', '804D', constants.PSETID_ADDRESS, overrideClass = EntryID.autoCreate)
+ return self._getNamedAs('804D', ps.PSETID_ADDRESS, EntryID.autoCreate)
- @property
+ @functools.cached_property
def birthdayLocal(self) -> Optional[datetime.datetime]:
"""
The birthday of the contact at 0:00 in the client's local time zone.
"""
- return self._ensureSetNamed('_birthdayLocal', '80DE', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80DE', ps.PSETID_ADDRESS)
- @property
- def businessCard(self) -> 'PIL.Image.Image':
+ @functools.cached_property
+ def businessCard(self) -> bytes:
+ if InsecureFeatures.PIL_IMAGE_PARSING not in self.insecureFeatures:
+ return SecurityError('PIL_IMAGE_PARSING must be enabled to create a business card image.')
# First import PIL here so it's an optional dependency.
try:
import PIL.Image
@@ -148,30 +122,32 @@ def businessCard(self) -> 'PIL.Image.Image':
imDraw = PIL.ImageDraw.ImageDraw(im)
# Create the border of the image:
- imDraw.rectangle(((0, 0,), (249, 149)), outline = (109, 109, 109))
+ imDraw.rectangle(((0, 0), (249, 149)), outline = (109, 109, 109))
# Finally, return the image.
- return im
+ out = io.BytesIO()
+ im.save(out, 'png')
+ return out
- @property
+ @functools.cached_property
def businessCardCardPicture(self) -> Optional[bytes]:
"""
The image to be used on a business card. Must be either a PNG file or a
JPEG file.
"""
- return self._ensureSetNamed('_businessCardCardPicture', '8041', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8041', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def businessCardDisplayDefinition(self) -> Optional[BusinessCardDisplayDefinition]:
"""
Specifies the customization details for displaying a contact as a
business card.
"""
- return self._ensureSetNamed('_businessCardDisplayDefinition', '8040', constants.PSETID_ADDRESS, overrideClass = BusinessCardDisplayDefinition)
+ return self._getNamedAs('8040', ps.PSETID_ADDRESS, BusinessCardDisplayDefinition)
- @property
- def businessFax(self) -> Optional[dict]:
+ @functools.cached_property
+ def businessFax(self) -> Optional[Dict]:
"""
Returns a dict of the data for the business fax. Returns None if no
fields are set.
@@ -179,243 +155,235 @@ def businessFax(self) -> Optional[dict]:
Keys are "address_type", "email_address", "number",
"original_display_name", and "original_entry_id".
"""
- try:
- return self._businessFax
- except AttributeError:
- data = {
- 'address_type': self.businessFaxAddressType,
- 'email_address': self.businessFaxEmailAddress,
- 'number': self.businessFaxNumber,
- 'original_display_name': self.businessFaxOriginalDisplayName,
- 'original_entry_id': self.businessFaxOriginalEntryId,
- }
- self._businessFax = data if any(data[x] for x in data) else None
- return self._businessFax
+ data = {
+ 'address_type': self.businessFaxAddressType,
+ 'email_address': self.businessFaxEmailAddress,
+ 'number': self.businessFaxNumber,
+ 'original_display_name': self.businessFaxOriginalDisplayName,
+ 'original_entry_id': self.businessFaxOriginalEntryId,
+ }
+ return data if any(data[x] for x in data) else None
- @property
+ @functools.cached_property
def businessFaxAddressType(self) -> Optional[str]:
"""
The type of address for the fax. MUST be set to "FAX" if present.
"""
- return self._ensureSetNamed('_businessFaxAddressType', '80C2', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80C2', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def businessFaxEmailAddress(self) -> Optional[str]:
"""
Contains a user-readable display name, followed by the "@" character,
followed by a fax number.
"""
- return self._ensureSetNamed('_businessFaxEmailAddress', '80C3', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80C3', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def businessFaxNumber(self) -> Optional[str]:
"""
Contains the number of the contact's business fax.
"""
- return self._ensureSet('_businessFaxNumber', '__substg1.0_3A24')
+ return self._getStringStream('__substg1.0_3A24')
- @property
+ @functools.cached_property
def businessFaxOriginalDisplayName(self) -> Optional[str]:
"""
The normalized subject for the contact.
"""
- return self._ensureSetNamed('_businessFaxOriginalDisplayName', '80C4', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80C4', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def businessFaxOriginalEntryId(self) -> Optional[EntryID]:
"""
The one-off EntryID corresponding to this fax address.
"""
- return self._ensureSetNamed('_businessFaxOriginalEntryId', '80C5', constants.PSETID_ADDRESS, overrideClass = EntryID.autoCreate)
+ return self._getNamedAs('80C5', ps.PSETID_ADDRESS, EntryID.autoCreate)
- @property
+ @functools.cached_property
def businessTelephoneNumber(self) -> Optional[str]:
"""
Contains the number of the contact's business telephone.
"""
- return self._ensureSet('_businessTelephoneNumber', '__substg1.0_3A08')
+ return self._getStringStream('__substg1.0_3A08')
- @property
+ @functools.cached_property
def businessTelephone2Number(self) -> Optional[Union[str, List[str]]]:
"""
Contains the second number or numbers of the contact's business.
"""
- return self._ensureSetTyped('_businessTelephone2Number', '3A1B')
+ return self._getTypedAs('3A1B')
- @property
+ @functools.cached_property
def businessHomePage(self) -> Optional[str]:
"""
Contains the url of the homepage of the contact's business.
"""
- return self._ensureSet('_businessHomePage', '__substg1.0_3A51')
+ return self._getStringStream('__substg1.0_3A51')
- @property
+ @functools.cached_property
def callbackTelephoneNumber(self) -> Optional[str]:
"""
Contains the contact's callback telephone number.
"""
- return self._ensureSet('_callbackTelephoneNumber', '__substg1.0_3A02')
+ return self._getStringStream('__substg1.0_3A02')
- @property
+ @functools.cached_property
def carTelephoneNumber(self) -> Optional[str]:
"""
Contains the number of the contact's car telephone.
"""
- return self._ensureSet('_carTelephoneNumber', '__substg1.0_3A1E')
+ return self._getStringStream('__substg1.0_3A1E')
- @property
+ @functools.cached_property
def childrensNames(self) -> Optional[List[str]]:
"""
A list of the named of the contact's children.
"""
- return self._ensureSetTyped('_childrensNames', '3A58')
+ return self._getTypedAs('3A58')
- @property
+ @functools.cached_property
def companyMainTelephoneNumber(self) -> Optional[str]:
"""
Contains the number of the main telephone of the contact's company.
"""
- return self._ensureSet('_companyMainTelephoneNumber', '__substg1.0_3A57')
+ return self._getStringStream('__substg1.0_3A57')
- @property
+ @functools.cached_property
def companyName(self) -> Optional[str]:
"""
The name of the company the contact works at.
"""
- return self._ensureSet('_companyName', '__substg1.0_3A16')
+ return self._getStringStream('__substg1.0_3A16')
- @property
+ @functools.cached_property
def computerNetworkName(self) -> Optional[str]:
"""
The name of the network to wwhich the contact's computer is connected.
"""
- return self._ensureSet('_computerNetworkName', '__substg1.0_3A49')
+ return self._getStringStream('__substg1.0_3A49')
- @property
+ @functools.cached_property
def contactCharacterSet(self) -> Optional[int]:
"""
The character set that is used for this Contact object.
"""
- return self._ensureSetNamed('_contactCharacterSet', '8023', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8023', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def contactItemData(self) -> Optional[List[int]]:
"""
Used to help display the contact information.
"""
- return self._ensureSetNamed('_contactItemData', '8007', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8007', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def contactLinkedGlobalAddressListEntryID(self) -> Optional[EntryID]:
"""
The EntryID of the GAL object to which the duplicate contact is linked.
"""
- return self._ensureSetNamed('_contactLinkedGlobalAddressListEntryID', '80E2', constants.PSETID_ADDRESS, overrideClass = EntryID.autoCreate)
+ return self._getNamedAs('80E2', ps.PSETID_ADDRESS, EntryID.autoCreate)
- @property
+ @functools.cached_property
def contactLinkGlobalAddressListLinkID(self) -> Optional[str]:
"""
The GUID of the GAL contact to which the duplicate contact is linked.
"""
- return self._ensureSetNamed('_contactLinkGlobalAddressListLinkId', '80E8', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80E8', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def contactLinkGlobalAddressListLinkState(self) -> Optional[ContactLinkState]:
"""
The state of linking between the GAL contact and the duplicate contact.
"""
- return self._ensureSetNamed('_contactLinkGlobalAddressListLinkState', '80E6', constants.PSETID_ADDRESS, overrideClass = ContactLinkState)
+ return self._getNamedAs('80E6', ps.PSETID_ADDRESS, ContactLinkState)
- @property
+ @functools.cached_property
def contactLinkLinkRejectHistory(self) -> Optional[List[bytes]]:
"""
A list of any contacts that were previously rejected for linking with
the duplicate contact.
"""
- return self._ensureSetNamed('_contactLinkLinkRejectHistory', '80E5', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80E5', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def contactLinkSMTPAddressCache(self) -> Optional[List[str]]:
"""
A list of the SMTP addresses that are used by the GAL contact that are
linked to the duplicate contact.
"""
- return self._ensureSetNamed('_contactLinkSMTPAddressCache', '80E3', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80E3', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def contactPhoto(self) -> Optional[bytes]:
"""
The contact photo, if it exists.
"""
- try:
- return self._contactPhoto
- except AttributeError:
- self._contactPhoto = None
- if self.hasPicture:
- if len(self.attachments) > 0:
- contactPhotoAtt = next((att for att in self.attachments if att.isAttachmentContactPhoto), None)
- if contactPhotoAtt:
- self._contactPhoto = contactPhotoAtt.data
- return self._contactPhoto
+ if self.hasPicture:
+ if len(self.attachments) > 0:
+ contactPhotoAtt = next((att for att in self.attachments if att.isAttachmentContactPhoto), None)
+ if contactPhotoAtt:
+ return contactPhotoAtt.data
+ return None
- @property
+ @functools.cached_property
def contactUserField1(self) -> Optional[str]:
"""
Used to store custom text for a business card.
"""
- return self._ensureSetNamed('_contactUserField1', '804F', constants.PSETID_ADDRESS)
+ return self._getNamedAs('804F', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def contactUserField2(self) -> Optional[str]:
"""
Used to store custom text for a business card.
"""
- return self._ensureSetNamed('_contactUserField2', '8050', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8050', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def contactUserField3(self) -> Optional[str]:
"""
Used to store custom text for a business card.
"""
- return self._ensureSetNamed('_contactUserField3', '8051', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8051', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def contactUserField4(self) -> Optional[str]:
"""
Used to store custom text for a business card.
"""
- return self._ensureSetNamed('_contactUserField4', '8052', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8052', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def customerID(self) -> Optional[str]:
"""
The contact's customer ID number.
"""
- return self._ensureSet('_customerID', '__substg1.0_3A4A')
+ return self._getStringStream('__substg1.0_3A4A')
- @property
+ @functools.cached_property
def departmentName(self) -> Optional[str]:
"""
The name of the department the contact works in.
"""
- return self._ensureSet('_departmentName', '__substg1.0_3A18')
+ return self._getStringStream('__substg1.0_3A18')
- @property
+ @functools.cached_property
def displayName(self) -> Optional[str]:
"""
The full name of the contact.
"""
- return self._ensureSet('_displayName', '__substg1.0_3001')
+ return self._getStringStream('__substg1.0_3001')
- @property
+ @functools.cached_property
def displayNamePrefix(self) -> Optional[str]:
"""
The contact's honorific title.
"""
- return self._ensureSet('_displayNamePrefix', '__substg1.0_3A45')
+ return self._getStringStream('__substg1.0_3A45')
- @property
- def email1(self) -> Optional[dict]:
+ @functools.cached_property
+ def email1(self) -> Optional[Dict]:
"""
Returns a dict of the data for email 1. Returns None if no fields are
set.
@@ -423,263 +391,245 @@ def email1(self) -> Optional[dict]:
Keys are "address_type", "display_name", "email_address",
"original_display_name", and "original_entry_id".
"""
- try:
- return self._email1
- except AttributeError:
- data = {
- 'address_type': self.email1AddressType,
- 'display_name': self.email1DisplayName,
- 'email_address': self.email1EmailAddress,
- 'original_display_name': self.email1OriginalDisplayName,
- 'original_entry_id': self.email1OriginalEntryId,
- }
- self._email1 = data if any(data[x] for x in data) else None
- return self._email1
+ data = {
+ 'address_type': self.email1AddressType,
+ 'display_name': self.email1DisplayName,
+ 'email_address': self.email1EmailAddress,
+ 'original_display_name': self.email1OriginalDisplayName,
+ 'original_entry_id': self.email1OriginalEntryId,
+ }
+ return data if any(data[x] for x in data) else None
- @property
+ @functools.cached_property
def email1AddressType(self) -> Optional[str]:
"""
The address type of the first email address.
"""
- return self._ensureSetNamed('_email1AddressType', '8082', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8082', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def email1DisplayName(self) -> Optional[str]:
"""
The user-readable display name of the first email address.
"""
- return self._ensureSetNamed('_email1DisplayName', '8080', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8080', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def email1EmailAddress(self) -> Optional[str]:
"""
The first email address.
"""
- return self._ensureSetNamed('_email1EmailAddress', '8083', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8083', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def email1OriginalDisplayName(self) -> Optional[str]:
"""
The first SMTP email address that corresponds to the first email address
for the contact.
"""
- return self._ensureSetNamed('_email1OriginalDisplayName', '8084', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8084', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def email1OriginalEntryId(self) -> Optional[EntryID]:
"""
The EntryID of the object correspinding to this electronic address.
"""
- return self._ensureSetNamed('_email1OriginalEntryId', '8085', constants.PSETID_ADDRESS, overrideClass = EntryID.autoCreate)
+ return self._getNamedAs('8085', ps.PSETID_ADDRESS, EntryID.autoCreate)
- @property
+ @functools.cached_property
def email2(self) -> Optional[dict]:
"""
Returns a dict of the data for email 2. Returns None if no fields are
set.
"""
- try:
- return self._email2
- except AttributeError:
- data = {
- 'address_type': self.email2AddressType,
- 'display_name': self.email2DisplayName,
- 'email_address': self.email2EmailAddress,
- 'original_display_name': self.email2OriginalDisplayName,
- 'original_entry_id': self.email2OriginalEntryId,
- }
- self._email2 = data if any(data[x] for x in data) else None
- return self._email2
+ data = {
+ 'address_type': self.email2AddressType,
+ 'display_name': self.email2DisplayName,
+ 'email_address': self.email2EmailAddress,
+ 'original_display_name': self.email2OriginalDisplayName,
+ 'original_entry_id': self.email2OriginalEntryId,
+ }
+ return data if any(data[x] for x in data) else None
- @property
+ @functools.cached_property
def email2AddressType(self) -> Optional[str]:
"""
The address type of the second email address.
"""
- return self._ensureSetNamed('_email2AddressType', '8092', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8092', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def email2DisplayName(self) -> Optional[str]:
"""
The user-readable display name of the second email address.
"""
- return self._ensureSetNamed('_email2DisplayName', '8090', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8090', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def email2EmailAddress(self) -> Optional[str]:
"""
The second email address.
"""
- return self._ensureSetNamed('_email2EmailAddress', '8093', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8093', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def email2OriginalDisplayName(self) -> Optional[str]:
"""
The second SMTP email address that corresponds to the second email address
for the contact.
"""
- return self._ensureSetNamed('_email2OriginalDisplayName', '8094', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8094', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def email2OriginalEntryId(self) -> Optional[EntryID]:
"""
The EntryID of the object correspinding to this electronic address.
"""
- return self._ensureSetNamed('_email2OriginalEntryId', '8095', constants.PSETID_ADDRESS, overrideClass = EntryID.autoCreate)
+ return self._getNamedAs('8095', ps.PSETID_ADDRESS, EntryID.autoCreate)
- @property
+ @functools.cached_property
def email3(self) -> Optional[dict]:
"""
Returns a dict of the data for email 3. Returns None if no fields are
set.
"""
- try:
- return self._email3
- except AttributeError:
- data = {
- 'address_type': self.email3AddressType,
- 'display_name': self.email3DisplayName,
- 'email_address': self.email3EmailAddress,
- 'original_display_name': self.email3OriginalDisplayName,
- 'original_entry_id': self.email3OriginalEntryId,
- }
- self._email3 = data if any(data[x] for x in data) else None
- return self._email3
+ data = {
+ 'address_type': self.email3AddressType,
+ 'display_name': self.email3DisplayName,
+ 'email_address': self.email3EmailAddress,
+ 'original_display_name': self.email3OriginalDisplayName,
+ 'original_entry_id': self.email3OriginalEntryId,
+ }
+ return data if any(data[x] for x in data) else None
- @property
+ @functools.cached_property
def email3AddressType(self) -> Optional[str]:
"""
The address type of the third email address.
"""
- return self._ensureSetNamed('_email3AddressType', '80A2', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80A2', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def email3DisplayName(self) -> Optional[str]:
"""
The user-readable display name of the third email address.
"""
- return self._ensureSetNamed('_email3DisplayName', '80A0', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80A0', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def email3EmailAddress(self) -> Optional[str]:
"""
The third email address.
"""
- return self._ensureSetNamed('_email3EmailAddress', '80A3', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80A3', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def email3OriginalDisplayName(self) -> Optional[str]:
"""
The third SMTP email address that corresponds to the third email address
for the contact.
"""
- return self._ensureSetNamed('_email3OriginalDisplayName', '80A4', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80A4', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def email3OriginalEntryId(self) -> Optional[EntryID]:
"""
The EntryID of the object correspinding to this electronic address.
"""
- return self._ensureSetNamed('_email3OriginalEntryId', '80A5', constants.PSETID_ADDRESS, overrideClass = EntryID.autoCreate)
+ return self._getNamedAs('80A5', ps.PSETID_ADDRESS, EntryID.autoCreate)
- @property
+ @functools.cached_property
def emails(self) -> Tuple[Union[Dict, None], Union[Dict, None], Union[Dict, None]]:
"""
Returns a tuple of all the email dicts. Value for an email will be None
if no fields were set.
"""
- try:
- return self._emails
- except AttributeError:
- self._emails = (self.email1, self.email2, self.email3)
- return self._emails
+ return (self.email1, self.email2, self.email3)
- @property
- def faxNumbers(self) -> Optional[dict]:
+ @functools.cached_property
+ def faxNumbers(self) -> Optional[Dict]:
"""
Returns a dictionary of the fax numbers. Entry will be None if no fields
were set.
Keys are "business", "home", and "primary".
"""
- try:
- return self._faxNumbers
- except AttributeError:
- self._faxNumbers = {
- 'business': self.businessFax,
- 'home': self.homeFax,
- 'primary': self.primaryFax,
- }
+ return {
+ 'business': self.businessFax,
+ 'home': self.homeFax,
+ 'primary': self.primaryFax,
+ }
- @property
+
+ @functools.cached_property
def fileUnder(self) -> Optional[str]:
"""
The name under which to file a contact when displaying a list of
contacts.
"""
- return self._ensureSetNamed('_fileUnder', '8005', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8005', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def fileUnderID(self) -> Optional[int]:
"""
The format to use for fileUnder. See PidLidFileUnderId in [MS-OXOCNTC]
for details.
"""
- return self._ensureSetNamed('_fileUnderID', '8006', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8006', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def freeBusyLocation(self) -> Optional[str]:
"""
A URL path from which a client can retrieve free/busy status information
for the contact as an iCalendat file.
"""
- return self._ensureSetNamed('_freeBusyLocation', '80D8', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80D8', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def ftpSite(self) -> Optional[str]:
"""
The contact's File Transfer Protocol url.
"""
- return self._ensureSet('_ftpSite', '__substg1.0_3A4C')
+ return self._getStringStream('__substg1.0_3A4C')
- @property
+ @functools.cached_property
def gender(self) -> Optional[Gender]:
"""
The gender of the contact.
"""
- return self._ensureSet('_gender', '__substg1.0_3A4D', overrideClass = Gender)
+ return self._getPropertyAs('3A4D0002', lambda x : Gender(x or 0), False)
- @property
+ @functools.cached_property
def generation(self) -> Optional[str]:
"""
A generational abbreviation that follows the full
name of the contact.
"""
- return self._ensureSet('_generation', '__substg1.0_3A05')
+ return self._getStringStream('__substg1.0_3A05')
- @property
+ @functools.cached_property
def givenName(self) -> Optional[str]:
"""
The first name of the contact.
"""
- return self._ensureSet('_givenName', '__substg1.0_3A06')
+ return self._getStringStream('__substg1.0_3A06')
- @property
+ @functools.cached_property
def governmentIDNumber(self) -> Optional[str]:
"""
The contact's government ID number.
"""
- return self._ensureSet('_governmentIDNumber', '__substg1.0_3A07')
+ return self._getStringStream('__substg1.0_3A07')
- @property
+ @functools.cached_property
def hasPicture(self) -> bool:
"""
Whether the contact has a contact photo.
"""
- return self._ensureSetNamed('_hasPicture', '8015', constants.PSETID_ADDRESS, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8015', ps.PSETID_ADDRESS, bool, False)
@property
- def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
+ def headerFormatProperties(self) -> HEADER_FORMAT_TYPE:
"""
Returns a dictionary of properties, in order, to be formatted into the
header. Keys are the names to use in the header while the values are one
@@ -770,71 +720,71 @@ def strListToStr(inp : Optional[Union[str, List[str]]]):
},
}
- @property
+ @functools.cached_property
def hobbies(self) -> Optional[str]:
"""
The hobies of the contact.
"""
- return self._ensureSet('_hobbies', '__substg1.0_3A43')
+ return self._getStringStream('__substg1.0_3A43')
- @property
+ @functools.cached_property
def homeAddress(self) -> Optional[str]:
"""
The complete home address of the contact.
"""
- return self._ensureSetNamed('_homeAddress', '801A', constants.PSETID_ADDRESS)
+ return self._getNamedAs('801A', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def homeAddressCountry(self) -> Optional[str]:
"""
The country portion of the contact's home address.
"""
- return self._ensureSet('_homeAddressCountry', '__substg1.0_3A5A')
+ return self._getStringStream('__substg1.0_3A5A')
- @property
+ @functools.cached_property
def homeAddressCountryCode(self) -> Optional[str]:
"""
The country code portion of the contact's home address.
"""
- return self._ensureSetNamed('_homeAddressCountryCode', '80DA', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80DA', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def homeAddressLocality(self) -> Optional[str]:
"""
The locality or city portion of the contact's home address.
"""
- return self._ensureSet('_homeAddressLocality', '__substg1.0_3A59')
+ return self._getStringStream('__substg1.0_3A59')
- @property
+ @functools.cached_property
def homeAddressPostalCode(self) -> Optional[str]:
"""
The postal code portion of the contact's home address.
"""
- return self._ensureSet('_homeAddressPostalCode', '__substg1.0_3A5B')
+ return self._getStringStream('__substg1.0_3A5B')
- @property
+ @functools.cached_property
def homeAddressPostOfficeBox(self) -> Optional[str]:
"""
The number or identifier of the contact's home post office box.
"""
- return self._ensureSet('_homeAddressPostOfficeBox', '__substg1.0_3A5E')
+ return self._getStringStream('__substg1.0_3A5E')
- @property
+ @functools.cached_property
def homeAddressStateOrProvince(self) -> Optional[str]:
"""
The state or province portion of the contact's home address.
"""
- return self._ensureSet('_homeAddressStateOrProvince', '__substg1.0_3A5C')
+ return self._getStringStream('__substg1.0_3A5C')
- @property
+ @functools.cached_property
def homeAddressStreet(self) -> Optional[str]:
"""
The street portion of the contact's home address.
"""
- return self._ensureSet('_homeAddressStreet', '__substg1.0_3A5D')
+ return self._getStringStream('__substg1.0_3A5D')
- @property
- def homeFax(self) -> Optional[dict]:
+ @functools.cached_property
+ def homeFax(self) -> Optional[Dict]:
"""
Returns a dict of the data for the home fax. Returns None if no fields
are set.
@@ -842,341 +792,337 @@ def homeFax(self) -> Optional[dict]:
Keys are "address_type", "email_address", "number",
"original_display_name", and "original_entry_id".
"""
- try:
- return self._homeFax
- except AttributeError:
- data = {
- 'address_type': self.homeFaxAddressType,
- 'email_address': self.homeFaxEmailAddress,
- 'number': self.homeFaxNumber,
- 'original_display_name': self.homeFaxOriginalDisplayName,
- 'original_entry_id': self.homeFaxOriginalEntryId,
- }
- self._homeFax = data if any(data[x] for x in data) else None
- return self._homeFax
+ data = {
+ 'address_type': self.homeFaxAddressType,
+ 'email_address': self.homeFaxEmailAddress,
+ 'number': self.homeFaxNumber,
+ 'original_display_name': self.homeFaxOriginalDisplayName,
+ 'original_entry_id': self.homeFaxOriginalEntryId,
+ }
+ return data if any(data[x] for x in data) else None
- @property
+ @functools.cached_property
def homeFaxAddressType(self) -> Optional[str]:
"""
The type of address for the fax. MUST be set to "FAX" if present.
"""
- return self._ensureSetNamed('_homeFaxAddressType', '80D2', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80D2', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def homeFaxEmailAddress(self) -> Optional[str]:
"""
Contains a user-readable display name, followed by the "@" character,
followed by a fax number.
"""
- return self._ensureSetNamed('_homeFaxEmailAddress', '80D3', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80D3', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def homeFaxNumber(self) -> Optional[str]:
"""
Contains the number of the contact's home fax.
"""
- return self._ensureSet('_homeFaxNumber', '__substg1.0_3A25')
+ return self._getStringStream('__substg1.0_3A25')
- @property
+ @functools.cached_property
def homeFaxOriginalDisplayName(self) -> Optional[str]:
"""
The normalized subject for the contact.
"""
- return self._ensureSetNamed('_homeFaxOriginalDisplayName', '80D4', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80D4', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def homeFaxOriginalEntryId(self) -> Optional[EntryID]:
"""
The one-off EntryID corresponding to this fax address.
"""
- return self._ensureSetNamed('_homeFaxOriginalEntryId', '80D5', constants.PSETID_ADDRESS, overrideClass = EntryID.autoCreate)
+ return self._getNamedAs('80D5', ps.PSETID_ADDRESS, EntryID.autoCreate)
- @property
+ @functools.cached_property
def homeTelephoneNumber(self) -> Optional[str]:
"""
The number of the contact's home telephone.
"""
- return self._ensureSet('_homeTelephoneNumber', '__substg1.0_3A09')
+ return self._getStringStream('__substg1.0_3A09')
- @property
+ @functools.cached_property
def homeTelephone2Number(self) -> Optional[Union[str, List[str]]]:
"""
The number(s) of the contact's second home telephone.
"""
- return self._ensureSetTyped('_homeTelephone2Number', '3A2F')
+ return self._getTypedAs('3A2F')
- @property
+ @functools.cached_property
def initials(self) -> Optional[str]:
"""
The initials of the contact.
"""
- return self._ensureSet('_initials', '__substg1.0_3A0A')
+ return self._getStringStream('__substg1.0_3A0A')
- @property
+ @functools.cached_property
def instantMessagingAddress(self) -> Optional[str]:
"""
The instant messaging address of the contact.
"""
- return self._ensureSetNamed('_instantMessagingAddress', '8062', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8062', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def isContactLinked(self) -> bool:
"""
Whether the contact is linked to other contacts.
"""
- return self._ensureSetNamed('_isContactLinked', '80E0', constants.PSETID_ADDRESS, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('80E0', ps.PSETID_ADDRESS, bool, False)
- @property
+ @functools.cached_property
def isdnNumber(self) -> Optional[str]:
"""
The Integrated Services Digital Network (ISDN) telephone number of the
contact.
"""
- return self._ensureSet('_isdnNumber', '__substg1.0_3A2D')
+ return self._getStringStream('__substg1.0_3A2D')
- @property
+ @functools.cached_property
def jobTitle(self) -> Optional[str]:
"""
The job title of the contact.
"""
- return self._ensureSet('_jobTitle', '__substg1.0_3A17')
+ return self._getStringStream('__substg1.0_3A17')
- @property
+ @functools.cached_property
def language(self) -> Optional[str]:
"""
The language that the contact uses.
"""
- return self._ensureSet('_language', '__substg1.0_3A0C')
+ return self._getStringStream('__substg1.0_3A0C')
- @property
+ @functools.cached_property
def lastModifiedBy(self) -> Optional[str]:
"""
The name of the last user to modify the contact file.
"""
- return self._ensureSet('_lastModifiedBy', '__substg1.0_3FFA')
+ return self._getStringStream('__substg1.0_3FFA')
- @property
+ @functools.cached_property
def location(self) -> Optional[str]:
"""
The location of the contact. For example, this could be the building or
office number of the contact.
"""
- return self._ensureSet('_location', '__substg1.0_3A0D')
+ return self._getStringStream('__substg1.0_3A0D')
- @property
+ @functools.cached_property
def mailAddress(self) -> Optional[str]:
"""
The complete mail address of the contact.
"""
- return self._ensureSet('_mailAddress', '__substg1.0_3A15')
+ return self._getStringStream('__substg1.0_3A15')
- @property
+ @functools.cached_property
def mailAddressCountry(self) -> Optional[str]:
"""
The country portion of the contact's mail address.
"""
- return self._ensureSet('_mailAddressCountry', '__substg1.0_3A26')
+ return self._getStringStream('__substg1.0_3A26')
- @property
+ @functools.cached_property
def mailAddressCountryCode(self) -> Optional[str]:
"""
The country code portion of the contact's mail address.
"""
- return self._ensureSetNamed('_mailAddressCountryCode', '80DD', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80DD', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def mailAddressLocality(self) -> Optional[str]:
"""
The locality or city portion of the contact's mail address.
"""
- return self._ensureSet('_mailAddressLocality', '__substg1.0_3A27')
+ return self._getStringStream('__substg1.0_3A27')
- @property
+ @functools.cached_property
def mailAddressPostalCode(self) -> Optional[str]:
"""
The postal code portion of the contact's mail address.
"""
- return self._ensureSet('_mailAddressPostalCode', '__substg1.0_3A2A')
+ return self._getStringStream('__substg1.0_3A2A')
- @property
+ @functools.cached_property
def mailAddressPostOfficeBox(self) -> Optional[str]:
"""
The number or identifier of the contact's mail post office box.
"""
- return self._ensureSet('_mailAddressPostOfficeBox', '__substg1.0_3A2B')
+ return self._getStringStream('__substg1.0_3A2B')
- @property
+ @functools.cached_property
def mailAddressStateOrProvince(self) -> Optional[str]:
"""
The state or province portion of the contact's mail address.
"""
- return self._ensureSet('_mailAddressStateOrProvince', '__substg1.0_3A28')
+ return self._getStringStream('__substg1.0_3A28')
- @property
+ @functools.cached_property
def mailAddressStreet(self) -> Optional[str]:
"""
The street portion of the contact's mail address.
"""
- return self._ensureSet('_mailAddressStreet', '__substg1.0_3A29')
+ return self._getStringStream('__substg1.0_3A29')
- @property
+ @functools.cached_property
def managerName(self) -> Optional[str]:
"""
The name of the contact's manager.
"""
- return self._ensureSet('_managerName', '__substg1.0_3A4E')
+ return self._getStringStream('__substg1.0_3A4E')
- @property
+ @functools.cached_property
def middleName(self) -> Optional[str]:
"""
The middle name(s) of the contact.
"""
- return self._ensureSet('_middleNames', '__substg1.0_3A44')
+ return self._getStringStream('__substg1.0_3A44')
- @property
+ @functools.cached_property
def mobileTelephoneNumber(self) -> Optional[str]:
"""
The mobile telephone number of the contact.
"""
- return self._ensureSet('_mobileTelephoneNumber', '__substg1.0_3A1C')
+ return self._getStringStream('__substg1.0_3A1C')
- @property
+ @functools.cached_property
def nickname(self) -> Optional[str]:
"""
The nickname of the contanct.
"""
- return self._ensureSet('_nickname', '__substg1.0_3A4F')
+ return self._getStringStream('__substg1.0_3A4F')
- @property
+ @functools.cached_property
def officeLocation(self) -> Optional[str]:
"""
The location of the office that the contact works in.
"""
- return self._ensureSet('_officeLocation', '__substg1.0_3A19')
+ return self._getStringStream('__substg1.0_3A19')
- @property
+ @functools.cached_property
def organizationalIDNumber(self) -> Optional[str]:
"""
The organizational ID number for the contact, such as an employee ID
number.
"""
- return self._ensureSet('_organizationalIdNumber', '__substg1.0_3A10')
+ return self._getStringStream('__substg1.0_3A10')
- @property
+ @functools.cached_property
def oscSyncEnabled(self) -> bool:
"""
Whether contact synchronization with an external source (such as a
social networking site) is handled by the server.
"""
- return self._ensureSetProperty('_oscSyncEnabled', '7C24000B', overrideClass = bool, preserveNone = False)
+ return self._getPropertyAs('7C24000B', bool, False)
- @property
+ @functools.cached_property
def otherAddress(self) -> Optional[str]:
"""
The complete other address of the contact.
"""
- return self._ensureSetNamed('_otherAddress', '801C', constants.PSETID_ADDRESS)
+ return self._getNamedAs('801C', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def otherAddressCountry(self) -> Optional[str]:
"""
The country portion of the contact's other address.
"""
- return self._ensureSet('_otherAddressCountry', '__substg1.0_3A60')
+ return self._getStringStream('__substg1.0_3A60')
- @property
+ @functools.cached_property
def otherAddressCountryCode(self) -> Optional[str]:
"""
The country code portion of the contact's other address.
"""
- return self._ensureSetNamed('_otherAddressCountryCode', '80DC', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80DC', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def otherAddressLocality(self) -> Optional[str]:
"""
The locality or city portion of the contact's other address.
"""
- return self._ensureSet('_otherAddressLocality', '__substg1.0_3A5F')
+ return self._getStringStream('__substg1.0_3A5F')
- @property
+ @functools.cached_property
def otherAddressPostalCode(self) -> Optional[str]:
"""
The postal code portion of the contact's other address.
"""
- return self._ensureSet('_otherAddressPostalCode', '__substg1.0_3A61')
+ return self._getStringStream('__substg1.0_3A61')
- @property
+ @functools.cached_property
def otherAddressPostOfficeBox(self) -> Optional[str]:
"""
The number or identifier of the contact's other post office box.
"""
- return self._ensureSet('_otherAddressPostOfficeBox', '__substg1.0_3A64')
+ return self._getStringStream('__substg1.0_3A64')
- @property
+ @functools.cached_property
def otherAddressStateOrProvince(self) -> Optional[str]:
"""
The state or province portion of the contact's other address.
"""
- return self._ensureSet('_otherAddressStateOrProvince', '__substg1.0_3A62')
+ return self._getStringStream('__substg1.0_3A62')
- @property
+ @functools.cached_property
def otherAddressStreet(self) -> Optional[str]:
"""
The street portion of the contact's other address.
"""
- return self._ensureSet('_otherAddressStreet', '__substg1.0_3A63')
+ return self._getStringStream('__substg1.0_3A63')
- @property
+ @functools.cached_property
def otherTelephoneNumber(self) -> Optional[str]:
"""
Contains the number of the contact's other telephone.
"""
- return self._ensureSet('_otherTelephoneNumber', '__substg1.0_3A1F')
+ return self._getStringStream('__substg1.0_3A1F')
- @property
+ @functools.cached_property
def pagerTelephoneNumber(self) -> Optional[str]:
"""
The contact's pager telephone number.
"""
- return self._ensureSet('_pagerTelephoneNumber', '__substg1.0_3A21')
+ return self._getStringStream('__substg1.0_3A21')
- @property
+ @functools.cached_property
def personalHomePage(self) -> Optional[str]:
"""
The contact's personal web page UL.
"""
- return self._ensureSet('_personalHomePage', '__substg1.0_3A50')
+ return self._getStringStream('__substg1.0_3A50')
- @property
+ @functools.cached_property
def phoneticCompanyName(self) -> Optional[str]:
"""
The phonetic pronunciation of the contact's company name.
"""
- return self._ensureSetNamed('_phoneticCompanyName', '802E', constants.PSETID_ADDRESS)
+ return self._getNamedAs('802E', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def phoneticGivenName(self) -> Optional[str]:
"""
The phonetic pronunciation of the contact's given name.
"""
- return self._ensureSetNamed('_phoneticGivenName', '802C', constants.PSETID_ADDRESS)
+ return self._getNamedAs('802C', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def phoneticSurname(self) -> Optional[str]:
"""
The phonetic pronunciation of the given name of the contact.
"""
- return self._ensureSetNamed('_phoneticSurname', '802D', constants.PSETID_ADDRESS)
+ return self._getNamedAs('802D', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def postalAddressID(self) -> PostalAddressID:
"""
Indicates which physical address is the Mailing Address for this
contact.
"""
- return self._ensureSetNamed('_postalAddressID', '8022', constants.PSETID_ADDRESS, overrideClass = lambda x : PostalAddressID(x or 0), preserveNone = False)
+ return self._getNamedAs('8022', ps.PSETID_ADDRESS, lambda x : PostalAddressID(x or 0), False)
- @property
+ @functools.cached_property
def primaryFax(self) -> Optional[dict]:
"""
Returns a dict of the data for the primary fax. Returns None if no
@@ -1185,210 +1131,206 @@ def primaryFax(self) -> Optional[dict]:
Keys are "address_type", "email_address", "number",
"original_display_name", and "original_entry_id".
"""
- try:
- return self._primaryFax
- except AttributeError:
- data = {
- 'address_type': self.primaryFaxAddressType,
- 'email_address': self.primaryFaxEmailAddress,
- 'number': self.primaryFaxNumber,
- 'original_display_name': self.primaryFaxOriginalDisplayName,
- 'original_entry_id': self.primaryFaxOriginalEntryId,
- }
- self._primaryFax = data if any(data[x] for x in data) else None
- return self._primaryFax
+ data = {
+ 'address_type': self.primaryFaxAddressType,
+ 'email_address': self.primaryFaxEmailAddress,
+ 'number': self.primaryFaxNumber,
+ 'original_display_name': self.primaryFaxOriginalDisplayName,
+ 'original_entry_id': self.primaryFaxOriginalEntryId,
+ }
+ return data if any(data[x] for x in data) else None
- @property
+ @functools.cached_property
def primaryFaxAddressType(self) -> Optional[str]:
"""
The type of address for the fax. MUST be set to "FAX" if present.
"""
- return self._ensureSetNamed('_primaryFaxAddressType', '80B2', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80B2', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def primaryFaxEmailAddress(self) -> Optional[str]:
"""
Contains a user-readable display name, followed by the "@" character,
followed by a fax number.
"""
- return self._ensureSetNamed('_primaryFaxEmailAddress', '80B3', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80B3', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def primaryFaxNumber(self) -> Optional[str]:
"""
Contains the number of the contact's primary fax.
"""
- return self._ensureSet('_primaryFaxNumber', '__substg1.0_3A23')
+ return self._getStringStream('__substg1.0_3A23')
- @property
+ @functools.cached_property
def primaryFaxOriginalDisplayName(self) -> Optional[str]:
"""
The normalized subject for the contact.
"""
- return self._ensureSetNamed('_primaryFaxOriginalDisplayName', '80B4', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80B4', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def primaryFaxOriginalEntryId(self) -> Optional[EntryID]:
"""
The one-off EntryID corresponding to this fax address.
"""
- return self._ensureSetNamed('_primaryFaxOriginalEntryId', '80B5', constants.PSETID_ADDRESS, overrideClass = EntryID.autoCreate)
+ return self._getNamedAs('80B5', ps.PSETID_ADDRESS, EntryID.autoCreate)
- @property
+ @functools.cached_property
def primaryTelephoneNumber(self) -> Optional[str]:
"""
Contains the number of the contact's primary telephone.
"""
- return self._ensureSet('_primaryTelephoneNumber', '__substg1.0_3A1A')
+ return self._getStringStream('__substg1.0_3A1A')
- @property
+ @functools.cached_property
def profession(self) -> Optional[str]:
"""
The profession of the contact.
"""
- return self._ensureSet('_profession', '__substg1.0_3A46')
+ return self._getStringStream('__substg1.0_3A46')
- @property
+ @functools.cached_property
def radioTelephoneNumber(self) -> Optional[str]:
"""
Contains the number of the contact's radio telephone.
"""
- return self._ensureSet('_radioTelephoneNumber', '__substg1.0_3A1D')
+ return self._getStringStream('__substg1.0_3A1D')
- @property
+ @functools.cached_property
def referenceEntryID(self) -> Optional[EntryID]:
"""
Contains a value that is equal to the value of the EntryID of the
Contact object unless the Contact object is a copy of an earlier
original.
"""
- return self._ensureSetNamed('_referenceEntryID', '85BD', constants.PSETID_COMMON, overrideClass = EntryID.autoCreate)
+ return self._getNamedAs('85BD', ps.PSETID_COMMON, EntryID.autoCreate)
- @property
+ @functools.cached_property
def referredByName(self) -> Optional[str]:
"""
The name of the person who referred this contact to the user.
"""
- return self._ensureSet('_referredByName', '__substg1.0_3A47')
+ return self._getStringStream('__substg1.0_3A47')
- @property
+ @functools.cached_property
def spouseName(self) -> Optional[str]:
"""
The name of the contact's spouse.
"""
- return self._ensureSet('_spouseName', '__substg1.0_3A48')
+ return self._getStringStream('__substg1.0_3A48')
- @property
+ @functools.cached_property
def surname(self) -> Optional[str]:
"""
The surname of the contact.
"""
- return self._ensureSet('_surname', '__substg1.0_3A11')
+ return self._getStringStream('__substg1.0_3A11')
- @property
+ @functools.cached_property
def tddTelephoneNumber(self) -> Optional[str]:
"""
The telephone number for the contact's text telephone (TTY) or
telecommunication device for the deaf (TDD).
"""
- return self._ensureSet('_tddTelephoneNumber', '__substg1.0_3A4B')
+ return self._getStringStream('__substg1.0_3A4B')
- @property
+ @functools.cached_property
def telexNumber(self) -> Optional[Union[str, List[str]]]:
"""
The contact's telex number(s).
"""
- return self._ensureSetTyped('_telexNumber', '3A2C')
+ return self._getTypedAs('3A2C')
- @property
+ @functools.cached_property
def userX509Certificate(self) -> Optional[List[bytes]]:
"""
A list of certificates for the contact.
"""
- return self._ensureSetTyped('_userX509Certificate', '3A70')
+ return self._getTypedAs('3A70')
- @property
+ @functools.cached_property
def weddingAnniversary(self) -> Optional[datetime.datetime]:
"""
The wedding anniversary of the contact at 11:59 UTC.
"""
- return self._ensureSetProperty('_weddingAnniversary', '3A410040')
+ return self._getPropertyAs('3A410040')
- @property
+ @functools.cached_property
def weddingAnniversaryEventEntryID(self) -> Optional[EntryID]:
"""
The EntryID of an optional Appointement object that represents the
contact's wedding anniversary.
"""
- return self._ensureSetNamed('_weddingAnniversaryEventEntryID', '804E', constants.PSETID_ADDRESS, overrideClass = EntryID.autoCreate)
+ return self._getNamedAs('804E', ps.PSETID_ADDRESS, EntryID.autoCreate)
- @property
+ @functools.cached_property
def weddingAnniversaryLocal(self) -> Optional[datetime.datetime]:
"""
The wedding anniversary of the contact at 0:00 in the client's local
time zone.
"""
- return self._ensureSetNamed('_weddingAnniversaryLocal', '80DF', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80DF', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def webpageUrl(self) -> Optional[str]:
"""
The contact's business web page url. SHOULD be the same as businessUrl.
"""
- return self._ensureSetNamed('_webpageUrl', '802B', constants.PSETID_ADDRESS)
+ return self._getNamedAs('802B', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def workAddress(self) -> Optional[str]:
"""
The complete work address of the contact.
"""
- return self._ensureSetNamed('_workAddress', '801B', constants.PSETID_ADDRESS)
+ return self._getNamedAs('801B', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def workAddressCountry(self) -> Optional[str]:
"""
The country portion of the contact's work address.
"""
- return self._ensureSetNamed('_workAddressCountry', '8049', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8049', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def workAddressCountryCode(self) -> Optional[str]:
"""
The country code portion of the contact's work address.
"""
- return self._ensureSetNamed('_workAddressCountryCode', '80DB', constants.PSETID_ADDRESS)
+ return self._getNamedAs('80DB', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def workAddressLocality(self) -> Optional[str]:
"""
The locality or city portion of the contact's work address.
"""
- return self._ensureSetNamed('_workAddressLocality', '8046', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8046', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def workAddressPostalCode(self) -> Optional[str]:
"""
The postal code portion of the contact's work address.
"""
- return self._ensureSetNamed('_workAddressPostalCode', '8048', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8048', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def workAddressPostOfficeBox(self) -> Optional[str]:
"""
The number or identifier of the contact's work post office box.
"""
- return self._ensureSetNamed('_workAddressPostOfficeBox', '804A', constants.PSETID_ADDRESS)
+ return self._getNamedAs('804A', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def workAddressStateOrProvince(self) -> Optional[str]:
"""
The state or province portion of the contact's work address.
"""
- return self._ensureSetNamed('_workAddressStateOrProvince', '8047', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8047', ps.PSETID_ADDRESS)
- @property
+ @functools.cached_property
def workAddressStreet(self) -> Optional[str]:
"""
The street portion of the contact's work address.
"""
- return self._ensureSetNamed('_workAddressStreet', '8045', constants.PSETID_ADDRESS)
+ return self._getNamedAs('8045', ps.PSETID_ADDRESS)
diff --git a/extract_msg/meeting_cancellation.py b/extract_msg/msg_classes/meeting_cancellation.py
similarity index 97%
rename from extract_msg/meeting_cancellation.py
rename to extract_msg/msg_classes/meeting_cancellation.py
index 1f96bc4a..2d184659 100644
--- a/extract_msg/meeting_cancellation.py
+++ b/extract_msg/msg_classes/meeting_cancellation.py
@@ -3,8 +3,8 @@
]
-from . import constants
-from .enums import RecurPatternType, ResponseStatus
+from .. import constants
+from ..enums import RecurPatternType, ResponseStatus
from .meeting_related import MeetingRelated
diff --git a/extract_msg/meeting_exception.py b/extract_msg/msg_classes/meeting_exception.py
similarity index 69%
rename from extract_msg/meeting_exception.py
rename to extract_msg/msg_classes/meeting_exception.py
index 09fbc6eb..3748be0c 100644
--- a/extract_msg/meeting_exception.py
+++ b/extract_msg/msg_classes/meeting_exception.py
@@ -4,10 +4,12 @@
import datetime
+import functools
from typing import Optional
-from . import constants
+from .. import constants
+from ..enums import SaveType
from .meeting_related import MeetingRelated
@@ -16,7 +18,7 @@ class MeetingException(MeetingRelated):
Class for handling Meeting Exceptions.
"""
- def save(self, *args, **kwargs):
+ def save(self, **_) -> constants.SAVE_TYPE:
"""
Meeting Exceptions are hidden attachments with no save behaviors. As
such, for saving we literally just return the object and do nothing
@@ -25,28 +27,28 @@ def save(self, *args, **kwargs):
If you want something to happen for saving, you can call the save of a
parent class or write your own code.
"""
- return self
+ return (SaveType.NONE, None)
- @property
+ @functools.cached_property
def exceptionReplaceTime(self) -> Optional[datetime.datetime]:
"""
The date and time within the recurrence pattern that the exception will
replace. The value is specified in UTC.
"""
- return self._ensureSetNamed('_exceptionReplaceTime', '8228', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8228', constants.ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def fExceptionalBody(self) -> bool:
"""
Indicates that the Exception Embedded Message object has a body that
differs from the Recurring Calendar object. If True, the Exception MUST
have a body.
"""
- return self._ensureSetNamed('_fExceptionalBody', '8206', constants.PSETID_APPOINTMENT, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8206', constants.ps.PSETID_APPOINTMENT, bool, False)
- @property
+ @functools.cached_property
def fInvited(self) -> bool:
"""
Indicates if invitations have been sent for this exception.
"""
- return self._ensureSetNamed('_fInvited', '8229', constants.PSETID_APPOINTMENT, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8229', constants.ps.PSETID_APPOINTMENT, bool, False)
diff --git a/extract_msg/meeting_forward.py b/extract_msg/msg_classes/meeting_forward.py
similarity index 91%
rename from extract_msg/meeting_forward.py
rename to extract_msg/msg_classes/meeting_forward.py
index c1e490c3..a4d0625f 100644
--- a/extract_msg/meeting_forward.py
+++ b/extract_msg/msg_classes/meeting_forward.py
@@ -3,11 +3,13 @@
]
+import functools
+
from typing import Optional
-from . import constants
+from .. import constants
from .meeting_related import MeetingRelated
-from .enums import RecurPatternType
+from ..enums import RecurPatternType
class MeetingForwardNotification(MeetingRelated):
@@ -15,7 +17,7 @@ class MeetingForwardNotification(MeetingRelated):
Class for handling Meeting Forward Notification objects.
"""
- @property
+ @functools.cached_property
def forwardNotificationRecipients(self) -> Optional[bytes]:
"""
Bytes containing a list of RecipientRow structures that indicate the
@@ -24,7 +26,7 @@ def forwardNotificationRecipients(self) -> Optional[bytes]:
Incomplete, looks to be the same structure as
appointmentUnsendableRecipients, so we need more examples of this.
"""
- return self._ensureSetNamed('_forwardNotificationRecipients', '8261', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8261', constants.ps.PSETID_APPOINTMENT)
@property
def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
@@ -88,10 +90,10 @@ def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
},
}
- @property
+ @functools.cached_property
def promptSendUpdate(self) -> bool:
"""
Indicates that the Meeting Forward Notification object was out-of-date
when it was received.
"""
- return self._ensureSetNamed('_promptSendUpdate', '8045', constants.PSETID_COMMON, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8045', constants.ps.PSETID_COMMON, bool, False)
diff --git a/extract_msg/msg_classes/meeting_related.py b/extract_msg/msg_classes/meeting_related.py
new file mode 100644
index 00000000..6d2f8bef
--- /dev/null
+++ b/extract_msg/msg_classes/meeting_related.py
@@ -0,0 +1,65 @@
+__all__ = [
+ 'MeetingRelated',
+]
+
+
+import datetime
+import functools
+
+from typing import Optional
+
+from ..constants import ps
+from .calendar_base import CalendarBase
+from ..enums import ServerProcessingAction
+
+
+class MeetingRelated(CalendarBase):
+ """
+ Base class for meeting-related objects.
+ """
+
+ @functools.cached_property
+ def attendeeCriticalChange(self) -> Optional[datetime.datetime]:
+ """
+ The date and time at which the meeting-related object was sent.
+ """
+ return self._getNamedAs('0001', ps.PSETID_MEETING)
+
+ @functools.cached_property
+ def processed(self) -> bool:
+ """
+ Indicates whether a client has processed a meeting-related object.
+ """
+ return self._getPropertyAs('7D01000B', bool, False)
+
+ @functools.cached_property
+ def serverProcessed(self) -> bool:
+ """
+ Indicates that the Meeting Request object or Meeting Update object has
+ been processed.
+ """
+ return self._getNamedAs('85CC', ps.PSETID_CALENDAR_ASSISTANT, bool, False)
+
+ @functools.cached_property
+ def serverProcessingActions(self) -> Optional[ServerProcessingAction]:
+ """
+ A union of which actions have been taken on the Meeting Request object
+ or Meeting Update object.
+ """
+ return self._getNamedAs('85CD', ps.PSETID_CALENDAR_ASSISTANT, ServerProcessingAction)
+
+ @functools.cached_property
+ def timeZone(self) -> Optional[int]:
+ """
+ Specifies information about the time zone of a recurring meeting.
+
+ See PidLidTimeZone in [MS-OXOCAL] for details.
+ """
+ return self._getNamedAs('000C', ps.PSETID_MEETING)
+
+ @functools.cached_property
+ def where(self) -> Optional[str]:
+ """
+ PidLidWhere. Should be the same as location.
+ """
+ return self._getNamedAs('0002', ps.PSETID_MEETING)
diff --git a/extract_msg/meeting_request.py b/extract_msg/msg_classes/meeting_request.py
similarity index 78%
rename from extract_msg/meeting_request.py
rename to extract_msg/msg_classes/meeting_request.py
index 4e4fd167..5893ea75 100644
--- a/extract_msg/meeting_request.py
+++ b/extract_msg/msg_classes/meeting_request.py
@@ -4,12 +4,13 @@
import datetime
+import functools
-from typing import List, Optional, Set
+from typing import Optional
-from . import constants
+from ..constants import HEADER_FORMAT_TYPE, ps
from .meeting_related import MeetingRelated
-from .enums import BusyStatus, MeetingObjectChange, MeetingType, RecurCalendarType, RecurPatternType, ResponseStatus
+from ..enums import BusyStatus, MeetingObjectChange, MeetingType, RecurCalendarType, RecurPatternType, ResponseStatus
class MeetingRequest(MeetingRelated):
@@ -17,45 +18,45 @@ class MeetingRequest(MeetingRelated):
Class for handling Meeting Request and Meeting Update objects.
"""
- @property
+ @functools.cached_property
def appointmentMessageClass(self) -> Optional[str]:
"""
Indicates the value of the PidTagMessageClass property of the Meeting
object that is to be generated from the Meeting Request object. MUST
start with "IPM.Appointment".
"""
- return self._ensureSetNamed('_appointmentMessageClass', '0024', constants.PSETID_MEETING)
+ return self._getNamedAs('0024', ps.PSETID_MEETING)
- @property
+ @functools.cached_property
def calendarType(self) -> Optional[RecurCalendarType]:
"""
The value of the CalendarType field from the PidLidAppointmentRecur
property if the Meeting Request object represents a recurring series or
an exception.
"""
- return self._ensureSetNamed('_calendarType', '001C', constants.PSETID_MEETING, overrideClass = RecurCalendarType)
+ return self._getNamedAs('001C', ps.PSETID_MEETING, RecurCalendarType)
- @property
- def changeHighlight(self) -> Optional[Set[MeetingObjectChange]]:
+ @functools.cached_property
+ def changeHighlight(self) -> Optional[MeetingObjectChange]:
"""
Soecifies a bit field that indicates how the Meeting object has been
changed.
- Returns a set of flags.
+ Returns a union of the set flags.
"""
- return self._ensureSetNamed('_changeHighlight', '8204', constants.PSETID_APPOINTMENT, overrideClass = MeetingObjectChange.fromBits)
+ return self._getNamedAs('8204', ps.PSETID_APPOINTMENT, MeetingObjectChange)
- @property
+ @functools.cached_property
def forwardInstance(self) -> bool:
"""
Indicates that the Meeting Request object represents an exception to a
recurring series, and it was forwarded (even when forwarded by the
organizer) rather than being an invitation sent by the organizer.
"""
- return self._ensureSetNamed('_forwardInstance', '820A', constants.PSETID_APPOINTMENT, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('820A', ps.PSETID_APPOINTMENT, bool, False)
@property
- def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
+ def headerFormatProperties(self) -> HEADER_FORMAT_TYPE:
"""
Returns a dictionary of properties, in order, to be formatted into the
header. Keys are the names to use in the header while the values are one
@@ -132,41 +133,41 @@ def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
}
- @property
+ @functools.cached_property
def intendedBusyStatus(self) -> Optional[BusyStatus]:
"""
The value of the busyStatus on the Meeting object in the organizer's
calendar at the time the Meeting Request object or Meeting Update object
was sent.
"""
- return self._ensureSetNamed('_intendedBusyStatus', '8224', constants.PSETID_APPOINTMENT, overrideClass = BusyStatus)
+ return self._getNamedAs('8224', ps.PSETID_APPOINTMENT, BusyStatus)
- @property
+ @functools.cached_property
def meetingType(self) -> Optional[MeetingType]:
"""
The type of Meeting Request object or Meeting Update object.
"""
- return self._ensureSetNamed('_meetingType', '0026', constants.PSETID_MEETING, overrideClass = MeetingType)
+ return self._getNamedAs('0026', ps.PSETID_MEETING, MeetingType)
- @property
+ @functools.cached_property
def oldLocation(self) -> Optional[str]:
"""
The original value of the location property before a meeting update.
"""
- return self._ensureSetNamed('_oldLocation', '0028', constants.PSETID_MEETING)
+ return self._getNamedAs('0028', ps.PSETID_MEETING)
- @property
+ @functools.cached_property
def oldWhenEndWhole(self) -> Optional[datetime.datetime]:
"""
The original value of the appointmentEndWhole property before a meeting
update.
"""
- return self._ensureSetNamed('_oldWhenEndWhole', '002A', constants.PSETID_MEETING)
+ return self._getNamedAs('002A', ps.PSETID_MEETING)
- @property
+ @functools.cached_property
def oldWhenStartWhole(self) -> Optional[datetime.datetime]:
"""
The original value of the appointmentStartWhole property before a
meeting update.
"""
- return self._ensureSetNamed('_oldWhenStartWhole', '0029', constants.PSETID_MEETING)
+ return self._getNamedAs('0029', ps.PSETID_MEETING)
diff --git a/extract_msg/meeting_response.py b/extract_msg/msg_classes/meeting_response.py
similarity index 61%
rename from extract_msg/meeting_response.py
rename to extract_msg/msg_classes/meeting_response.py
index 2d81f38d..67bd1d93 100644
--- a/extract_msg/meeting_response.py
+++ b/extract_msg/msg_classes/meeting_response.py
@@ -4,11 +4,12 @@
import datetime
+import functools
from typing import Optional
-from . import constants
-from .enums import ResponseType
+from ..constants import ps
+from ..enums import ResponseType
from .meeting_related import MeetingRelated
@@ -17,55 +18,55 @@ class MeetingResponse(MeetingRelated):
Class for handling meeting response objects.
"""
- @property
+ @functools.cached_property
def appointmentCounterProposal(self) -> bool:
"""
Indicates if the response is a counter proposal.
"""
- return self._ensureSetNamed('_appointmentCounterProposal', '8257', constants.PSETID_APPOINTMENT, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8257', ps.PSETID_APPOINTMENT, bool, False)
- @property
+ @functools.cached_property
def appointmentProposedDuration(self) -> Optional[int]:
"""
The proposed value for the appointmentDuration property for a counter
proposal.
"""
- return self._ensureSetNamed('_appointmentProposedDuration', '8256', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8256', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def appointmentProposedEndWhole(self) -> Optional[datetime.datetime]:
"""
The proposal value for the appointmentEndWhole property for a counter
proposal.
"""
- return self._ensureSetNamed('_appointmentProposedEndWhole', '8251', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8251', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def appointmentProposedStartWhole(self) -> Optional[datetime.datetime]:
"""
The proposal value for the appointmentStartWhole property for a counter
proposal.
"""
- return self._ensureSetNamed('_appointmentProposedStartWhole', '8250', constants.PSETID_APPOINTMENT)
+ return self._getNamedAs('8250', ps.PSETID_APPOINTMENT)
- @property
+ @functools.cached_property
def isSilent(self) -> bool:
"""
Indicates if the user did not include any text in the body of the
Meeting Response object.
"""
- return self._ensureSetNamed('_isSilent', '0004', constants.PSETID_MEETING, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('0004', ps.PSETID_MEETING, bool, False)
- @property
+ @functools.cached_property
def promptSendUpdate(self) -> bool:
"""
Indicates that the Meeting Response object was out-of-date when it was
received.
"""
- return self._ensureSetNamed('_promptSendUpdate', '8045', constants.PSETID_COMMON, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8045', ps.PSETID_COMMON, bool, False)
- @property
- def responseType(self) -> Optional[ResponseType]:
+ @functools.cached_property
+ def responseType(self) -> ResponseType:
"""
The type of Meeting Response object.
"""
diff --git a/extract_msg/msg_classes/message.py b/extract_msg/msg_classes/message.py
new file mode 100644
index 00000000..79f62f75
--- /dev/null
+++ b/extract_msg/msg_classes/message.py
@@ -0,0 +1,12 @@
+__all__ = [
+ 'Message',
+]
+
+
+from .message_base import MessageBase
+
+
+class Message(MessageBase):
+ """
+ Parser for Microsoft Outlook message files.
+ """
diff --git a/extract_msg/message_base.py b/extract_msg/msg_classes/message_base.py
similarity index 64%
rename from extract_msg/message_base.py
rename to extract_msg/msg_classes/message_base.py
index f126a923..8907c9e8 100644
--- a/extract_msg/message_base.py
+++ b/extract_msg/msg_classes/message_base.py
@@ -1,6 +1,3 @@
-from __future__ import annotations
-
-
__all__ = [
'MessageBase',
]
@@ -20,25 +17,26 @@
import zipfile
import bs4
-import chardet
import compressed_rtf
import RTFDE
from email.parser import Parser as EmailParser
from typing import Callable, List, Optional, Union
-from . import constants
-from ._rtf.create_doc import createDocument
-from ._rtf.inject_rtf import injectStartRTF
-from .enums import BodyTypes, DeencapType, RecipientType
-from .exceptions import (
- BadHtmlError, DataNotFoundError, DeencapMalformedData,
- DeencapNotEncapsulated, IncompatibleOptionsError, WKError
+from .. import constants
+from .._rtf.create_doc import createDocument
+from .._rtf.inject_rtf import injectStartRTF
+from ..enums import (
+ BodyTypes, DeencapType, ErrorBehavior, RecipientType, SaveType
+ )
+from ..exceptions import (
+ DataNotFoundError, DeencapMalformedData, DeencapNotEncapsulated,
+ IncompatibleOptionsError, WKError
)
from .msg import MSGFile
-from .structures.report_tag import ReportTag
-from .recipient import Recipient
-from .utils import (
+from ..structures.report_tag import ReportTag
+from ..recipient import Recipient
+from ..utils import (
addNumToDir, addNumToZipDir, createZipOpen, decodeRfc2047, findWk,
htmlSanitize, inputToBytes, inputToString, isEncapsulatedRtf,
prepareFilename, rtfSanitizeHtml, rtfSanitizePlain, validateHtml
@@ -58,31 +56,11 @@ class MessageBase(MSGFile):
def __init__(self, path, **kwargs):
"""
- :param path: path to the msg file in the system or is the raw msg file.
- :param prefix: used for extracting embeded msg files
- inside the main one. Do not set manually unless
- you know what you are doing.
- :param parentMsg: Used for syncronizing named properties instances. Do
- not set this unless you know what you are doing.
- :param attachmentClass: Optional, the class the Message object
- will use for attachments. You probably should
- not change this value unless you know what you
- are doing.
- :param filename: Optional, the filename to be used by default when
- saving.
- :param delayAttachments: Optional, delays the initialization of
- attachments until the user attempts to retrieve them. Allows MSG
- files with bad attachments to be initialized so the other data can
- be retrieved.
- :param overrideEncoding: Optional, an encoding to use instead of the one
- specified by the msg file. Do not report encoding errors caused by
- this.
- :param attachmentErrorBehavior: Optional, the behavior to use in the
- event of an error when parsing the attachments.
+ Supports all of the options from :method MSGFile.__init__: with some
+ additional ones.
+
:param recipientSeparator: Optional, separator string to use between
recipients.
- :param ignoreRtfDeErrors: Optional, specifies that any errors that occur
- from the usage of RTFDE should be ignored (default: False).
:param deencapsulationFunc: Optional, if specified must be a callable
that will override the way that HTML/text is deencapsulated from the
RTF body. This function must take exactly 2 arguments, the first
@@ -95,78 +73,87 @@ def __init__(self, path, **kwargs):
internally or they will not be caught. The original deencapsulation
method will not run if this is set.
"""
+ if 'ignoreRtfDeErrors' in kwargs:
+ import warnings
+ warnings.warn(':param ignoreRtfDeErrors: is deprecated. Use :param ErrorBehavior: instead.', DeprecationWarning)
+
+ if kwargs.get('ignoreRtfDeErrors', False):
+ errorBehavior = kwargs.get('errorBehavior', ErrorBehavior.THROW)
+ errorBehavior |= ErrorBehavior.RTFDE
+ kwargs['errorBehavior'] = errorBehavior
+
super().__init__(path, **kwargs)
- recipientSeparator = ';'
- self.__recipientSeparator = kwargs.get('recipientSeparator', ';')
- self.__ignoreRtfDeErrors = kwargs.get('ignoreRtfDeErrors', False)
- self.__deencap = kwargs.get('deencapsulationFunc')
- # Initialize properties in the order that is least likely to cause bugs.
- # TODO have each function check for initialization of needed data so
- # these lines will be unnecessary.
- self.props
- self.header
- self.recipients
-
- self.to
- self.cc
- self.sender
- self.date
- # This variable keeps track of what the new line character should be.
- self.__crlf = '\n'
+ # The rest needs to be in a try-except block to ensure the file closes
+ # if an error occurs.
try:
- self.body
- except Exception as e:
- # Prevent an error in the body from preventing opening.
- logger.exception('Critical error accessing the body. File opened but accessing the body will throw an exception.')
- self.named
- self.namedProperties
+ self.__recipientSeparator = kwargs.get('recipientSeparator', ';')
+ self.__deencap = kwargs.get('deencapsulationFunc')
+ # Initialize properties in the order that is least likely to cause bugs.
+ # TODO have each function check for initialization of needed data so
+ # these lines will be unnecessary.
+ self.props
+ self.header
+ self.recipients
+
+ self.to
+ self.cc
+ self.sender
+ self.date
+ # This variable keeps track of what the new line character should be.
+ self._crlf = '\n'
+ try:
+ self.body
+ except Exception as e:
+ # Prevent an error in the body from preventing opening.
+ logger.exception('Critical error accessing the body. File opened but accessing the body will throw an exception.')
+ self.named
+ self.namedProperties
+ except:
+ try:
+ self.close()
+ except:
+ pass
+ raise
def _genRecipient(self, recipientType, recipientInt : RecipientType) -> Optional[str]:
"""
Returns the specified recipient field.
"""
- private = '_' + recipientType
recipientInt = RecipientType(recipientInt)
- try:
- return getattr(self, private)
- except AttributeError:
- value = None
- # Check header first.
- if self.headerInit():
- value = self.header[recipientType]
- if value:
- value = decodeRfc2047(value)
- value = value.replace(',', self.__recipientSeparator)
-
- # If the header had a blank field or didn't have the field, generate
- # it manually.
- if not value:
- # Check if the header has initialized.
- if self.headerInit():
- logger.info(f'Header found, but "{recipientType}" is not included. Will be generated from other streams.')
-
- # Get a list of the recipients of the specified type.
- foundRecipients = tuple(recipient.formatted for recipient in self.recipients if recipient.type == recipientInt)
-
- # If we found recipients, join them with the recipient separator
- # and a space.
- if len(foundRecipients) > 0:
- value = (self.__recipientSeparator + ' ').join(foundRecipients)
-
- # Code to fix the formatting so it's all a single line. This allows
- # the user to format it themself if they want. This should probably
- # be redone to use re or something, but I can do that later. This
- # shouldn't be a huge problem for now.
+ value = None
+ # Check header first.
+ if self.headerInit():
+ value = self.header[recipientType]
if value:
- value = value.replace(' \r\n\t', ' ').replace('\r\n\t ', ' ').replace('\r\n\t', ' ')
- value = value.replace('\r\n', ' ').replace('\r', ' ').replace('\n', ' ')
- while value.find(' ') != -1:
- value = value.replace(' ', ' ')
+ value = decodeRfc2047(value)
+ value = value.replace(',', self.__recipientSeparator)
+
+ # If the header had a blank field or didn't have the field, generate
+ # it manually.
+ if not value:
+ # Check if the header has initialized.
+ if self.headerInit():
+ logger.info(f'Header found, but "{recipientType}" is not included. Will be generated from other streams.')
+
+ # Get a list of the recipients of the specified type.
+ foundRecipients = tuple(recipient.formatted for recipient in self.recipients if recipient.type == recipientInt)
- # Set the field in the class.
- setattr(self, private, value)
+ # If we found recipients, join them with the recipient separator
+ # and a space.
+ if len(foundRecipients) > 0:
+ value = (self.__recipientSeparator + ' ').join(foundRecipients)
- return value
+ # Code to fix the formatting so it's all a single line. This allows
+ # the user to format it themself if they want. This should probably
+ # be redone to use re or something, but I can do that later. This
+ # shouldn't be a huge problem for now.
+ if value:
+ value = value.replace(' \r\n\t', ' ').replace('\r\n\t ', ' ').replace('\r\n\t', ' ')
+ value = value.replace('\r\n', ' ').replace('\r', ' ').replace('\n', ' ')
+ while value.find(' ') != -1:
+ value = value.replace(' ', ' ')
+
+ return value
def deencapsulateBody(self, rtfBody : bytes, bodyType : DeencapType) -> Optional[Union[bytes, str]]:
"""
@@ -201,7 +188,7 @@ def deencapsulateBody(self, rtfBody : bytes, bodyType : DeencapType) -> Optional
logger.exception('Custom deencapsulation function reported data is not encapsulated.')
else:
if self.deencapsulatedRtf and self.deencapsulatedRtf.content_type == 'html':
- return self.deencapsulatedRtf.html.encode('utf-8')
+ return self.deencapsulatedRtf.html
if bodyType == DeencapType.PLAIN:
logger.info('Could not deencapsulate plain text from RTF body.')
@@ -294,7 +281,7 @@ def getJson(self) -> str:
'body': decode_utf7(self.body),
})
- def getSaveBody(self, **kwargs) -> bytes:
+ def getSaveBody(self, **_) -> bytes:
"""
Returns the plain text body that will be used in saving based on the
arguments.
@@ -313,7 +300,7 @@ def getSaveBody(self, **kwargs) -> bytes:
header = self.getInjectableHeader(prefix, joinStr, suffix, formatter).encode('utf-8')
return header + inputToBytes(self.body, 'utf-8')
- def getSaveHtmlBody(self, preparedHtml : bool = False, charset : str = 'utf-8', **kwargs) -> bytes:
+ def getSaveHtmlBody(self, preparedHtml : bool = False, charset : str = 'utf-8', **_) -> bytes:
"""
Returns the HTML body that will be used in saving based on the
arguments.
@@ -327,9 +314,6 @@ def getSaveHtmlBody(self, preparedHtml : bool = False, charset : str = 'utf-8',
`None` or an empty string to not insert the tag (Default: 'utf-8').
:param kwargs: Used to allow kwargs expansion in the save function.
Arguments absorbed by this are simply ignored.
-
- :raises BadHtmlError: if :param preparedHtml: is False and the HTML
- fails to validate.
"""
if self.htmlBody:
# Inject the header into the data.
@@ -364,7 +348,7 @@ def getSaveHtmlBody(self, preparedHtml : bool = False, charset : str = 'utf-8',
else:
return self.htmlBody
- def getSavePdfBody(self, **kwargs) -> bytes:
+ def getSavePdfBody(self, wkPath = None, wkOptions = None, **kwargs) -> bytes:
"""
Returns the PDF body that will be used in saving based on the arguments.
@@ -383,10 +367,9 @@ def getSavePdfBody(self, **kwargs) -> bytes:
:raises WKError: Something went wrong in creating the PDF body.
"""
# Immediately try to find the executable.
- wkPath = findWk(kwargs.get('wkPath'))
+ wkPath = findWk(wkPath)
# First thing is first, we need to parse our wkOptions if they exist.
- wkOptions = kwargs.get('wkOptions')
if wkOptions:
try:
# Try to convert to a list, whatever it is, and fail if it is
@@ -423,7 +406,7 @@ def getSavePdfBody(self, **kwargs) -> bytes:
return process.stdout
- def getSaveRtfBody(self, **kwargs) -> bytes:
+ def getSaveRtfBody(self, **_) -> bytes:
"""
Returns the RTF body that will be used in saving based on the arguments.
@@ -452,8 +435,6 @@ def injectHtmlHeader(self, prepared : bool = False) -> bytes:
the prepared HTML (True) body (Default: False).
:raises AttributeError: if the correct HTML body cannot be acquired.
- :raises BadHtmlError: if :param preparedHtml: is False and the HTML fails to
- validate.
"""
if not self.htmlBody:
raise AttributeError('Cannot inject the HTML header without an HTML body attribute.')
@@ -472,18 +453,15 @@ def injectHtmlHeader(self, prepared : bool = False) -> bytes:
# Validate the HTML.
if not validateHtml(body):
- # If we are not preparing the HTML body, then raise an
- # exception.
- if not prepared:
- raise BadHtmlError('HTML body failed to pass validation.')
-
- # If we are here, then we need to do what we can to fix the HTML body.
- # Unfortunately this gets complicated because of the various ways the
- # body could be wrong. If only the tag is missing, then we just
- # need to insert it at the end and be done. If both the and
- # tag are missing, we determine where to put the body tag (around
- # everything if there is no tag, otherwise at the end) and then
- # wrap it all in the tag.
+ logger.warning('HTML body failed to validate. Code will attempt to correct it.')
+
+ # If we are here, then we need to do what we can to fix the HTML
+ # body. Unfortunately this gets complicated because of the various
+ # ways the body could be wrong. If only the tag is missing,
+ # then we just need to insert it at the end and be done. If both
+ # the and tag are missing, we determine where to put
+ # the body tag (around everything if there is no tag,
+ # otherwise at the end) and then wrap it all in the tag.
parser = bs4.BeautifulSoup(body, features = 'html.parser')
if not parser.find('html') and not parser.find('body'):
if parser.find('head') or parser.find('footer'):
@@ -558,7 +536,7 @@ def replace(bodyMarker):
return bodyMarker.group() + self.htmlInjectableHeader.encode('utf-8')
# Use the previously defined function to inject the HTML header.
- return constants.RE_HTML_BODY_START.sub(replace, body, 1)
+ return constants.re.HTML_BODY_START.sub(replace, body, 1)
def injectRtfHeader(self) -> bytes:
"""
@@ -587,7 +565,7 @@ def replace(bodyMarker):
# This first method only applies to documents with encapsulated HTML
# that is formatted in a nice way.
if isEncapsulatedRtf(self.rtfBody):
- data = constants.RE_RTF_ENC_BODY_START.sub(replace, self.rtfBody, 1)
+ data = constants.re.RTF_ENC_BODY_START.sub(replace, self.rtfBody, 1)
if data != self.rtfBody:
logger.debug('Successfully injected RTF header using encapsulation method.')
return data
@@ -598,7 +576,7 @@ def replace(bodyMarker):
logger.debug('Using _rtf module to inject RTF text header.')
return createDocument(injectStartRTF(self.rtfBody, injectableHeader))
- def save(self, **kwargs) -> MessageBase:
+ def save(self, **kwargs) -> constants.SAVE_TYPE:
"""
Saves the message body and attachments found in the message.
@@ -699,171 +677,177 @@ def save(self, **kwargs) -> MessageBase:
if pdf:
kwargs['preparedHtml'] = True
- # ZipFile handling.
- if _zip:
- # `raw` and `zip` are incompatible.
- if raw:
- raise IncompatibleOptionsError('The options `raw` and `zip` are incompatible.')
- # If we are doing a zip file, first check that we have been given a
- # path.
- if isinstance(_zip, (str, pathlib.Path)):
- # If we have a path then we use the zip file.
- _zip = zipfile.ZipFile(_zip, 'a', zipfile.ZIP_DEFLATED)
- kwargs['zip'] = _zip
- createdZip = True
- else:
- createdZip = False
- # Path needs to be done in a special way if we are in a zip file.
- path = pathlib.Path(kwargs.get('customPath', ''))
- # Set the open command to be that of the zip file.
- _open = createZipOpen(_zip.open)
- # Zip files use w for writing in binary.
- mode = 'w'
- else:
- path = pathlib.Path(kwargs.get('customPath', '.')).absolute()
- mode = 'wb'
- _open = open
-
- # Reset this for sub save calls.
- kwargs['customFilename'] = None
-
- # Check if incompatible options have been provided in any way.
- if _json + html + rtf + raw + attachOnly + pdf > 1:
- raise IncompatibleOptionsError('Only one of the following options may be used at a time: json, raw, html, rtf, attachmentsOnly, pdf.')
-
- # TODO: insert code here that will handle checking all of the msg files
- # to see if the path with overflow.
-
- if customFilename:
- # First we need to validate it. If there are invalid characters,
- # this will detect it.
- if constants.RE_INVALID_FILENAME_CHARACTERS.search(customFilename):
- raise ValueError('Invalid character found in customFilename. Must not contain any of the following characters: \\/:*?"<>|')
- # Quick fix to remove spaces from the end of the filename, if any
- # are there.
- customFilename = customFilename.strip()
- path /= customFilename[:maxNameLength]
- elif useMsgFilename:
- if not self.filename:
- raise ValueError(':param useMsgFilename: is only available if you are using an msg file on the disk or have provided a filename.')
- # Get the actual name of the file.
- filename = os.path.split(self.filename)[1]
- # Remove the extensions.
- filename = os.path.splitext(filename)[0]
- # Prepare the filename by removing any special characters.
- filename = prepareFilename(filename)
- # Shorted the filename.
- filename = filename[:maxNameLength]
- # Check to make sure we actually have a filename to use.
- if not filename:
- raise ValueError(f'Invalid filename found in self.filename: "{self.filename}"')
-
- # Add the file name to the path.
- path /= filename[:maxNameLength]
- else:
- path /= self.defaultFolderName[:maxNameLength]
+ # Try to get the body, if needed, before messing with the path.
+ if not attachOnly:
+ # Check what to save the body with.
+ fext = 'json' if _json else 'txt'
+
+ fallbackToPlain = False
+ useHtml = False
+ usePdf = False
+ useRtf = False
+ if html:
+ if self.htmlBody:
+ useHtml = True
+ fext = 'html'
+ elif not allowFallback:
+ if skipBodyNotFound:
+ fext = None
+ else:
+ raise DataNotFoundError('Could not find the htmlBody.')
+
+ if pdf:
+ if self.htmlBody:
+ usePdf = True
+ fext = 'pdf'
+ elif not allowFallback:
+ if skipBodyNotFound:
+ fext = None
+ else:
+ raise DataNotFoundError('Count not find the htmlBody to convert to pdf.')
- # Create the folders.
- if not _zip:
- try:
- os.makedirs(path)
- except Exception:
- newDirName = addNumToDir(path)
- if newDirName:
- path = newDirName
- else:
- raise Exception(f'Failed to create directory "{path}". Does it already exist?')
- else:
- # In my testing I ended up with multiple files in a zip at the same
- # location so let's try to handle that.
- pathCompare = str(path).replace('\\', '/').rstrip('/') + '/'
- if any(x.startswith(pathCompare) for x in _zip.namelist()):
- newDirName = addNumToZipDir(path, _zip)
- if newDirName:
- path = newDirName
+ if rtf or (html and not useHtml) or (pdf and not usePdf):
+ if self.rtfBody:
+ useRtf = True
+ fext = 'rtf'
+ elif not allowFallback:
+ if skipBodyNotFound:
+ fext = None
+ else:
+ raise DataNotFoundError('Could not find the rtfBody.')
else:
- raise Exception(f'Failed to create directory "{path}". Does it already exist?')
+ # This was the last resort before plain text, so fall
+ # back to that.
+ fallbackToPlain = True
+
+ # After all other options, try to go with plain text if
+ # possible.
+ if not (rtf or html or pdf) or fallbackToPlain:
+ # We need to check if the plain text body was found. If it
+ # was found but was empty that is considered valid, so we
+ # specifically check against None.
+ if self.body is None:
+ if skipBodyNotFound:
+ fext = None
+ else:
+ if allowFallback:
+ raise DataNotFoundError('Could not find a valid body using current options.')
+ else:
+ raise DataNotFoundError('Plain text body could not be found.')
- # Update the kwargs.
- kwargs['customPath'] = path
+ createdZip = False
+ try:
+ # ZipFile handling.
+ if _zip:
+ # `raw` and `zip` are incompatible.
+ if raw:
+ raise IncompatibleOptionsError('The options `raw` and `zip` are incompatible.')
+ # If we are doing a zip file, first check that we have been
+ # given a path.
+ if isinstance(_zip, (str, pathlib.Path)):
+ # If we have a path then we use the zip file.
+ _zip = zipfile.ZipFile(_zip, 'a', zipfile.ZIP_DEFLATED)
+ kwargs['zip'] = _zip
+ createdZip = True
+ # Path needs to be done in a special way if we are in a zip
+ # file.
+ path = pathlib.Path(kwargs.get('customPath', ''))
+ # Set the open command to be that of the zip file.
+ _open = createZipOpen(_zip.open)
+ # Zip files use w for writing in binary.
+ mode = 'w'
+ else:
+ path = pathlib.Path(kwargs.get('customPath', '.')).absolute()
+ mode = 'wb'
+ _open = open
+
+ # Reset this for sub save calls.
+ kwargs['customFilename'] = None
+
+ # Check if incompatible options have been provided in any way.
+ if _json + html + rtf + raw + attachOnly + pdf > 1:
+ raise IncompatibleOptionsError('Only one of the following options may be used at a time: json, raw, html, rtf, attachmentsOnly, pdf.')
+
+ # TODO: insert code here that will handle checking all of the msg
+ # files to see if the path with overflow.
+
+ if customFilename:
+ # First we need to validate it. If there are invalid characters,
+ # this will detect it.
+ if constants.re.INVALID_FILENAME_CHARS.search(customFilename):
+ raise ValueError('Invalid character found in customFilename. Must not contain any of the following characters: \\/:*?"<>|')
+ # Quick fix to remove spaces from the end of the filename, if
+ # any are there.
+ customFilename = customFilename.strip()
+ path /= customFilename[:maxNameLength]
+ elif useMsgFilename:
+ if not self.filename:
+ raise ValueError(':param useMsgFilename: is only available if you are using an msg file on the disk or have provided a filename.')
+ # Get the actual name of the file.
+ filename = os.path.split(self.filename)[1]
+ # Remove the extensions.
+ filename = os.path.splitext(filename)[0]
+ # Prepare the filename by removing any special characters.
+ filename = prepareFilename(filename)
+ # Shorted the filename.
+ filename = filename[:maxNameLength]
+ # Check to make sure we actually have a filename to use.
+ if not filename:
+ raise ValueError(f'Invalid filename found in self.filename: "{self.filename}"')
+
+ # Add the file name to the path.
+ path /= filename[:maxNameLength]
+ else:
+ path /= self.defaultFolderName[:maxNameLength]
- if raw:
- self.saveRaw(path)
- return self
+ # Create the folders.
+ if not _zip:
+ try:
+ os.makedirs(path)
+ except Exception:
+ newDirName = addNumToDir(path)
+ if newDirName:
+ path = newDirName
+ else:
+ raise OSError(f'Failed to create directory "{path}". Does it already exist?')
+ else:
+ # In my testing I ended up with multiple files in a zip at the
+ # same location so let's try to handle that.
+ pathCompare = str(path).replace('\\', '/').rstrip('/') + '/'
+ if any(x.startswith(pathCompare) for x in _zip.namelist()):
+ newDirName = addNumToZipDir(path, _zip)
+ if newDirName:
+ path = newDirName
+ else:
+ raise Exception(f'Failed to create directory "{path}". Does it already exist?')
- # If the user has requested the headers for this file, save it now.
- if kwargs.get('saveHeader', False):
- headerText = self.headerText
- if not headerText:
- headerText = constants.HEADER_FORMAT.format(subject = self.subject, **self.header)
+ # Update the kwargs.
+ kwargs['customPath'] = path
- with _open(str(path / 'header.txt'), mode) as f:
- f.write(headerText.encode('utf-8'))
+ if raw:
+ self.saveRaw(path)
+ return (SaveType.FOLDER, str(path))
- try:
- if not attachOnly:
- # Check what to save the body with.
- fext = 'json' if _json else 'txt'
-
- fallbackToPlain = False
- useHtml = False
- usePdf = False
- useRtf = False
- if html:
- if self.htmlBody:
- useHtml = True
- fext = 'html'
- elif not allowFallback:
- if skipBodyNotFound:
- fext = None
- else:
- raise DataNotFoundError('Could not find the htmlBody.')
-
- if pdf:
- if self.htmlBody:
- usePdf = True
- fext = 'pdf'
- elif not allowFallback:
- if skipBodyNotFound:
- fext = None
- else:
- raise DataNotFoundError('Count not find the htmlBody to convert to pdf.')
-
- if rtf or (html and not useHtml) or (pdf and not usePdf):
- if self.rtfBody:
- useRtf = True
- fext = 'rtf'
- elif not allowFallback:
- if skipBodyNotFound:
- fext = None
- else:
- raise DataNotFoundError('Could not find the rtfBody.')
- else:
- # This was the last resort before plain text, so fall
- # back to that.
- fallbackToPlain = True
-
- # After all other options, try to go with plain text if
- # possible.
- if not (rtf or html or pdf) or fallbackToPlain:
- # We need to check if the plain text body was found. If it
- # was found but was empty that is considered valid, so we
- # specifically check against None.
- if self.body is None:
- if skipBodyNotFound:
- fext = None
- else:
- if allowFallback:
- raise DataNotFoundError('Could not find a valid body using current options.')
- else:
- raise DataNotFoundError('Plain text body could not be found.')
+ # If the user has requested the headers for this file, save it now.
+ if kwargs.get('saveHeader', False):
+ headerText = self.headerText
+ if not headerText:
+ headerText = constants.HEADER_FORMAT.format(subject = self.subject, **self.header)
+
+ with _open(str(path / 'header.txt'), mode) as f:
+ f.write(headerText.encode('utf-8'))
if not skipAttachments:
# Save the attachments.
- attachmentNames = [attachment.save(**kwargs) for attachment in self.attachments if not (skipHidden and attachment.hidden)]
- # Remove skipped attachments.
- attachmentNames = [x for x in attachmentNames if x and isinstance(x, str)]
+ attachmentReturns = [attachment.save(**kwargs) for attachment in self.attachments if not (skipHidden and attachment.hidden)]
+ # Get the names from each.
+ attachmentNames = []
+ for x in attachmentReturns:
+ if isinstance(x[1], str):
+ attachmentNames.append(x[1])
+ elif isinstance(x[1], list):
+ attachmentNames.extend(x[1])
if not attachOnly and fext:
with _open(str(path / ('message.' + fext)), mode) as f:
@@ -872,7 +856,7 @@ def save(self, **kwargs) -> MessageBase:
if not skipAttachments:
emailObj['attachments'] = attachmentNames
- f.write(inputToBytes(json.dumps(emailObj), 'utf-8'))
+ f.write(json.dumps(emailObj).encode('utf-8'))
elif useHtml:
f.write(self.getSaveHtmlBody(**kwargs))
elif usePdf:
@@ -881,58 +865,54 @@ def save(self, **kwargs) -> MessageBase:
f.write(self.getSaveRtfBody(**kwargs))
else:
f.write(self.getSaveBody(**kwargs))
+
+ return (SaveType.FOLDER, str(path))
finally:
# Close the ZipFile if this function created it.
if _zip and createdZip:
_zip.close()
- # Return the instance so that functions can easily be chained.
- return self
-
- @property
+ @functools.cached_property
def bcc(self) -> Optional[str]:
"""
Returns the bcc field, if it exists.
"""
return self._genRecipient('bcc', RecipientType.BCC)
- @property
+ @functools.cached_property
def body(self) -> Optional[str]:
"""
Returns the message body, if it exists.
"""
- try:
- return self._body
- except AttributeError:
- if self._ensureSet('_body', '__substg1.0_1000'):
- pass
- else:
- # If the body doesn't exist, see if we can get it from the RTF
- # body.
- if self.rtfBody:
- self._body = self.deencapsulateBody(self.rtfBody, DeencapType.PLAIN)
+ # If the body exists but is empty, that means it should be returned.
+ if (body := self._getStringStream('__substg1.0_1000')) is not None:
+ pass
+ elif self.rtfBody:
+ # If the body doesn't exist, see if we can get it from the RTF
+ # body.
+ body = self.deencapsulateBody(self.rtfBody, DeencapType.PLAIN)
- if self._body:
- self._body = inputToString(self._body, 'utf-8')
- a = re.search('\n', self._body)
- if a is not None:
- if re.search('\r\n', self._body) is not None:
- self.__crlf = '\r\n'
- return self._body
+ if body:
+ body = inputToString(body, 'utf-8')
+ if re.search('\n', body) is not None:
+ if re.search('\r\n', body) is not None:
+ self._crlf = '\r\n'
- @property
+ return body
+
+ @functools.cached_property
def cc(self) -> Optional[str]:
"""
Returns the cc field, if it exists.
"""
return self._genRecipient('cc', RecipientType.CC)
- @property
+ @functools.cached_property
def compressedRtf(self) -> Optional[bytes]:
"""
Returns the compressed RTF stream, if it exists.
"""
- return self._ensureSet('_compressedRtf', '__substg1.0_10090102', False)
+ return self._getStream('__substg1.0_10090102')
@property
def crlf(self) -> str:
@@ -940,19 +920,14 @@ def crlf(self) -> str:
Returns the value of self.__crlf, should you need it for whatever
reason.
"""
- self.body
- return self.__crlf
+ return self._crlf
- @property
- def date(self) -> Optional[str]:
+ @functools.cached_property
+ def date(self) -> Optional[datetime.datetime]:
"""
Returns the send date, if it exists.
"""
- try:
- return self._date
- except AttributeError:
- self._date = self._prop.date if self.isSent else None
- return self._date
+ return self.props.date if self.isSent else None
@property
def deencapsulatedRtf(self) -> Optional[RTFDE.DeEncapsulator]:
@@ -973,41 +948,20 @@ def deencapsulatedRtf(self) -> Optional[RTFDE.DeEncapsulator]:
body = body[:-1]
try:
- try:
- self._deencapsultor = RTFDE.DeEncapsulator(body)
- except UnicodeDecodeError:
- # There is a known issue that bytes are not well decoded
- # by RTFDE right now, so let's see if we can't manually
- # decode it and see if that will work.
- #
- # There is also the fact that it is decoded *at all*
- # before binary data is stripped out. This data should
- # almost certainly be stripped out, so let's log it and
- # then log if we removed any of them before trying this.
- logger.warning(f'RTFDE failed to decode rtfBody for message with subject "{self.subject}". Attempting to cut out unnecessary data and override decoding.')
-
- match = constants.RE_BIN.search(body)
- # Because we are going to be actively removing things,
- # we want to search the entire thing over again.
- while match:
- logger.info(f'Found match to bin data starting at location {match.start()}. Replacing with nothing.')
- length = int(match.group(1))
- # Extract the entire binary section and replace it.
- body = body.replace(body[match.start():match.end() + length], b'', 1)
- match = constants.RE_BIN.search(body)
-
- self._deencapsultor = RTFDE.DeEncapsulator(body.decode(chardet.detect(body)['encoding']))
+ self._deencapsultor = RTFDE.DeEncapsulator(body)
self._deencapsultor.deencapsulate()
except RTFDE.exceptions.NotEncapsulatedRtf as e:
logger.debug('RTF body is not encapsulated.')
self._deencapsultor = None
except RTFDE.exceptions.MalformedEncapsulatedRtf as _e:
+ if ErrorBehavior.RTFDE_MALFORMED not in self.errorBehavior:
+ raise
logger.info('RTF body contains malformed encapsulated content.')
self._deencapsultor = None
except Exception:
# If we are just ignoring the errors, log it then set to
# None. Otherwise, continue the exception.
- if not self.__ignoreRtfDeErrors:
+ if ErrorBehavior.RTFDE_UNKNOWN_ERROR not in self.errorBehavior:
raise
logger.exception('Unhandled error happened while using RTFDE. You have choosen to ignore these errors.')
self._deencapsultor = None
@@ -1047,33 +1001,29 @@ def detectedBodies(self) -> BodyTypes:
return bodies
- @property
+ @functools.cached_property
def header(self) -> email.message.Message:
"""
Returns the message header, if it exists. Otherwise it will generate
one.
"""
- try:
- return self._header
- except AttributeError:
- headerText = self.headerText
- if headerText:
- self._header = EmailParser().parsestr(headerText)
- self._header['date'] = self.date
- else:
- logger.info('Header is empty or was not found. Header will be generated from other streams.')
- header = EmailParser().parsestr('')
- header.add_header('Date', self.date)
- header.add_header('From', self.sender)
- header.add_header('To', self.to)
- header.add_header('Cc', self.cc)
- header.add_header('Bcc', self.bcc)
- header.add_header('Message-Id', self.messageId)
- # TODO find authentication results outside of header
- header.add_header('Authentication-Results', None)
- self._header = header
-
- return self._header
+ headerText = self.headerText
+ if headerText:
+ header = EmailParser().parsestr(headerText)
+ header['date'] = self.date
+ else:
+ logger.info('Header is empty or was not found. Header will be generated from other streams.')
+ header = EmailParser().parsestr('')
+ header.add_header('Date', self.date)
+ header.add_header('From', self.sender)
+ header.add_header('To', self.to)
+ header.add_header('Cc', self.cc)
+ header.add_header('Bcc', self.bcc)
+ header.add_header('Message-Id', self.messageId)
+ # TODO find authentication results outside of header
+ header.add_header('Authentication-Results', None)
+
+ return header
@property
def headerDict(self) -> dict:
@@ -1139,34 +1089,30 @@ def headerText(self) -> Optional[str]:
"""
return self._getStringStream('__substg1.0_007D')
- @property
+ @functools.cached_property
def htmlBody(self) -> Optional[bytes]:
"""
Returns the html body, if it exists.
"""
- try:
- return self._htmlBody
- except AttributeError:
- if self._ensureSet('_htmlBody', '__substg1.0_10130102', False):
- # Reducing line repetition.
- pass
- elif self.rtfBody:
- logger.info('HTML body was not found, attempting to generate from RTF.')
- self._htmlBody = self.deencapsulateBody(self.rtfBody, DeencapType.HTML)
- # This is it's own if statement so we can ensure it will generate
- # even if there is an rtfBody, in the event it doesn't have HTML.
- if not self._htmlBody and self.body:
- # Convert the plain text body to html.
- logger.info('HTML body was not found, attempting to generate from plain text body.')
- correctedBody = html.escape(self.body).replace('\r', '').replace('\n', '
')
- self._htmlBody = f'{correctedBody}'.encode('utf-8')
-
- if not self._htmlBody:
- logger.info('HTML body could not be found nor generated.')
-
- return self._htmlBody
+ if (htmlBody := self._getStream('__substg1.0_10130102')) is not None:
+ pass
+ elif self.rtfBody:
+ logger.info('HTML body was not found, attempting to generate from RTF.')
+ htmlBody = self.deencapsulateBody(self.rtfBody, DeencapType.HTML)
+ # This is it's own if statement so we can ensure it will generate
+ # even if there is an rtfBody, in the event it doesn't have HTML.
+ if not htmlBody and self.body:
+ # Convert the plain text body to html.
+ logger.info('HTML body was not found, attempting to generate from plain text body.')
+ correctedBody = html.escape(self.body).replace('\r', '').replace('\n', '
')
+ htmlBody = f'{correctedBody}'.encode('utf-8')
- @property
+ if not htmlBody:
+ logger.info('HTML body could not be found nor generated.')
+
+ return htmlBody
+
+ @functools.cached_property
def htmlBodyPrepared(self) -> Optional[bytes]:
"""
Returns the HTML body that has (where possible) the embedded attachments
@@ -1194,7 +1140,7 @@ def htmlBodyPrepared(self) -> Optional[bytes]:
return soup.prettify('utf-8')
- @property
+ @functools.cached_property
def htmlInjectableHeader(self) -> str:
"""
The header that can be formatted and injected into the html body.
@@ -1206,21 +1152,24 @@ def htmlInjectableHeader(self) -> str:
return self.getInjectableHeader(prefix, joinStr, suffix, formatter)
- @property
+ @functools.cached_property
def inReplyTo(self) -> Optional[str]:
"""
Returns the message id that this message is in reply to.
"""
- return self._ensureSet('_in_reply_to', '__substg1.0_1042')
+ return self._getStringStream('__substg1.0_1042')
- @property
+ @functools.cached_property
def isRead(self) -> bool:
"""
Returns if this email has been marked as read.
"""
- return bool(self.props['0E070003'].value & 1)
+ try:
+ return bool(self.props['0E070003'].value & 1)
+ except (AttributeError, KeyError):
+ return False
- @property
+ @functools.cached_property
def isSent(self) -> bool:
"""
Returns if this email has been marked as sent. Assumes True if no flags
@@ -1231,79 +1180,62 @@ def isSent(self) -> bool:
else:
return not bool(self.props['0E070003'].value & 8)
- @property
+ @functools.cached_property
def messageId(self) -> Optional[str]:
- try:
- return self._messageId
- except AttributeError:
- headerResult = None
- if self.headerInit():
- headerResult = self.header['message-id']
- if headerResult is not None:
- self._messageId = headerResult
- else:
- if self.headerInit():
- logger.info('Header found, but "Message-Id" is not included. Will be generated from other streams.')
- self._messageId = self._getStringStream('__substg1.0_1035')
- return self._messageId
+ headerResult = None
+ if self.headerInit():
+ headerResult = self.header['message-id']
+ if headerResult is not None:
+ return headerResult
- @property
+ if self.headerInit():
+ logger.info('Header found, but "Message-Id" is not included. Will be generated from other streams.')
+ return self._getStringStream('__substg1.0_1035')
+
+ @functools.cached_property
def parsedDate(self):
return email.utils.parsedate(self.date)
- @property
+ @functools.cached_property
def receivedTime(self) -> Optional[datetime.datetime]:
"""
The date and time the message was received by the server.
"""
- return self._ensureSetProperty('_receivedTime', '0E060040')
+ return self._getPropertyAs('0E060040')
@property
def recipientSeparator(self) -> str:
return self.__recipientSeparator
- @property
+ @functools.cached_property
def recipients(self) -> List[Recipient]:
"""
Returns a list of all recipients.
"""
- try:
- return self._recipients
- except AttributeError:
- # Get the recipients
- recipientDirs = []
- prefixLen = self.prefixLen
- for dir_ in self.listDir():
- if dir_[prefixLen].startswith('__recip') and\
- dir_[prefixLen] not in recipientDirs:
- recipientDirs.append(dir_[prefixLen])
+ recipientDirs = []
+ prefixLen = self.prefixLen
+ for dir_ in self.listDir():
+ if dir_[prefixLen].startswith('__recip') and\
+ dir_[prefixLen] not in recipientDirs:
+ recipientDirs.append(dir_[prefixLen])
- self._recipients = []
+ return [Recipient(recipientDir, self) for recipientDir in recipientDirs]
- for recipientDir in recipientDirs:
- self._recipients.append(Recipient(recipientDir, self))
-
- return self._recipients
-
- @property
+ @functools.cached_property
def reportTag(self) -> Optional[ReportTag]:
"""
Data that is used to correlate the report and the original message.
"""
- return self._ensureSet('_reportTag', '__substg1.0_00310102', False, overrideClass = ReportTag)
+ return self._getStreamAs('__substg1.0_00310102', False, ReportTag)
- @property
+ @functools.cached_property
def rtfBody(self) -> Optional[bytes]:
"""
Returns the decompressed Rtf body from the message.
"""
- try:
- return self._rtfBody
- except AttributeError:
- self._rtfBody = compressed_rtf.decompress(self.compressedRtf) if self.compressedRtf else None
- return self._rtfBody
+ return compressed_rtf.decompress(self.compressedRtf) if self.compressedRtf else None
- @property
+ @functools.cached_property
def rtfEncapInjectableHeader(self) -> bytes:
"""
The header that can be formatted and injected into the plain RTF body.
@@ -1315,57 +1247,53 @@ def rtfEncapInjectableHeader(self) -> bytes:
return self.getInjectableHeader(prefix, joinStr, suffix, formatter).encode('utf-8')
- @property
+ @functools.cached_property
def rtfPlainInjectableHeader(self) -> bytes:
"""
The header that can be formatted and injected into the encapsulated RTF
body.
"""
prefix = '{'
- suffix = r'\par\par}'
- joinStr = r'\line'
+ suffix = '\\par\\par}'
+ joinStr = '\\line'
formatter = (lambda name, value : fr'{{\b {name}: \b0 {inputToString(rtfSanitizePlain(value), self.stringEncoding)}}}')
return self.getInjectableHeader(prefix, joinStr, suffix, formatter).encode('utf-8')
- @property
+ @functools.cached_property
def sender(self) -> Optional[str]:
"""
Returns the message sender, if it exists.
"""
- try:
- return self._sender
- except AttributeError:
- # Check header first
- if self.headerInit():
- headerResult = self.header['from']
- if headerResult is not None:
- self._sender = decodeRfc2047(headerResult)
- return headerResult
- logger.info('Header found, but "sender" is not included. Will be generated from other streams.')
- # Extract from other fields
- text = self._getStringStream('__substg1.0_0C1A')
- email = self._getStringStream('__substg1.0_5D01')
- # Will not give an email address sometimes. Seems to exclude the email address if YOU are the sender.
- result = None
- if text is None:
- result = email
- else:
- result = text
- if email is not None:
- result += ' <' + email + '>'
+ # Check header first
+ if self.headerInit():
+ headerResult = self.header['from']
+ if headerResult is not None:
+ return decodeRfc2047(headerResult)
+ logger.info('Header found, but "sender" is not included. Will be generated from other streams.')
+ # Extract from other fields
+ text = self._getStringStream('__substg1.0_0C1A')
+ email = self._getStringStream('__substg1.0_5D01')
+ # Will not give an email address sometimes. Seems to exclude the email
+ # address if YOU are the sender.
+ result = None
+ if text is None:
+ result = email
+ else:
+ result = text
+ if email is not None:
+ result += ' <' + email + '>'
- self._sender = result
- return result
+ return result
- @property
+ @functools.cached_property
def subject(self) -> Optional[str]:
"""
Returns the message subject, if it exists.
"""
- return self._ensureSet('_subject', '__substg1.0_0037')
+ return self._getStringStream('__substg1.0_0037')
- @property
+ @functools.cached_property
def to(self) -> Optional[str]:
"""
Returns the to field, if it exists.
diff --git a/extract_msg/message_signed.py b/extract_msg/msg_classes/message_signed.py
similarity index 100%
rename from extract_msg/message_signed.py
rename to extract_msg/msg_classes/message_signed.py
diff --git a/extract_msg/msg_classes/message_signed_base.py b/extract_msg/msg_classes/message_signed_base.py
new file mode 100644
index 00000000..08bcc297
--- /dev/null
+++ b/extract_msg/msg_classes/message_signed_base.py
@@ -0,0 +1,149 @@
+__all__ = [
+ 'MessageSignedBase',
+]
+
+
+import functools
+import html
+import logging
+import re
+
+from typing import List, Optional
+
+from ..enums import DeencapType, ErrorBehavior
+from ..exceptions import StandardViolationError
+from .message_base import MessageBase
+from ..attachments import SignedAttachment
+from ..utils import inputToBytes, inputToString, unwrapMultipart
+
+
+logger = logging.getLogger(__name__)
+logger.addHandler(logging.NullHandler())
+
+
+class MessageSignedBase(MessageBase):
+ """
+ Base class for Message like msg files.
+ """
+
+ def __init__(self, path, **kwargs):
+ """
+ Supports all of the options from :method MessageBase.__init__: with some
+ additional ones.
+
+ :param signedAttachmentClass: optional, the class the object will use
+ for signed attachments.
+ """
+ self.__signedAttachmentClass = kwargs.get('signedAttachmentClass', SignedAttachment)
+ super().__init__(path, **kwargs)
+
+ @property
+ def attachments(self) -> List:
+ """
+ Returns a list of all attachments.
+
+ :raises StandardViolationError: The standard for signed messages was
+ blatantly violated.
+ """
+ try:
+ return self._sAttachments
+ except AttributeError:
+ atts = super().attachments
+
+ if len(atts) != 1:
+ if ErrorBehavior.STANDARDS_VIOLATION in self.errorBehavior:
+ if len(atts) == 0:
+ logger.error('Signed message has no attachments, a violation of the standard.')
+ self._sAttachments = []
+ self._signedBody = None
+ self._signedHtmlBody = None
+ return
+ # If there is at least one attachment, just try to use the
+ # first.
+ else:
+ raise StandardViolationError('Signed messages without exactly 1 (regular) attachment constitue a violation of the standard.')
+
+ # We need to unwrap the multipart stream.
+ unwrapped = unwrapMultipart(atts[0].data)
+
+ # Now store everything where it needs to be and make the
+ # attachments.
+ self._sAttachments = [self.__signedAttachmentClass(self, **att) for att in unwrapped['attachments']]
+ self._signedBody = unwrapped['plain_body']
+ self._signedHtmlBody = inputToBytes(unwrapped['html_body'], 'utf-8')
+
+ return self._sAttachments
+
+ @functools.cached_property
+ def body(self) -> Optional[str]:
+ """
+ Returns the message body, if it exists.
+ """
+ if (body := self._getStringStream('__substg1.0_1000')) is not None:
+ pass
+ elif self.signedBody:
+ body = self.signedBody
+ elif self.rtfBody:
+ # If the body doesn't exist, see if we can get it from the RTF
+ # body.
+ body = self.deencapsulateBody(self.rtfBody, DeencapType.PLAIN)
+
+ if body:
+ body = inputToString(body, 'utf-8')
+ if re.search('\n', body):
+ if re.search('\r\n', body):
+ self._crlf = '\r\n'
+
+ return body
+
+ @functools.cached_property
+ def htmlBody(self) -> Optional[bytes]:
+ """
+ Returns the html body, if it exists.
+ """
+ if (htmlBody := self._getStream('__substg1.0_10130102')) is not None:
+ pass
+ elif self.signedHtmlBody:
+ htmlBody = self.signedHtmlBody
+ elif self.rtfBody:
+ logger.info('HTML body was not found, attempting to generate from RTF.')
+ htmlBody = self.deencapsulateBody(self.rtfBody, DeencapType.HTML)
+ elif self.body:
+ # Convert the plain text body to html.
+ logger.info('HTML body was not found, attempting to generate from plain text body.')
+ correctedBody = html.escpae(self.body).replace('\r', '').replace('\n', '
')
+ htmlBody = f'{correctedBody}'.encode('utf-8')
+ else:
+ logger.info('HTML body could not be found nor generated.')
+
+ return htmlBody
+
+ @functools.cached_property
+ def _rawAttachments(self) -> List:
+ """
+ A property to allow access to the non-signed attachments.
+ """
+ return super().attachments
+
+ @property
+ def signedAttachmentClass(self):
+ """
+ The attachment class used for signed attachments.
+ """
+ return self.__signedAttachmentClass
+
+ @functools.cached_property
+ def signedBody(self) -> Optional[str]:
+ """
+ Returns the body from the signed message if it exists.
+ """
+ self.attachments
+ return self._signedBody
+
+ @functools.cached_property
+ def signedHtmlBody(self) -> Optional[bytes]:
+ """
+ Returns the HTML body from the signed message if it exists.
+ """
+ self.attachments
+ return self._signedHtmlBody
diff --git a/extract_msg/msg.py b/extract_msg/msg_classes/msg.py
similarity index 68%
rename from extract_msg/msg.py
rename to extract_msg/msg_classes/msg.py
index 06afbc57..4b23f330 100644
--- a/extract_msg/msg.py
+++ b/extract_msg/msg_classes/msg.py
@@ -1,3 +1,6 @@
+from __future__ import annotations
+
+
__all__ = [
'MSGFile',
]
@@ -6,32 +9,37 @@
import codecs
import copy
import datetime
+import functools
import io
import logging
import os
import pathlib
+import weakref
import zipfile
import olefile
-from typing import List, Optional, Set, Tuple, Union
+from typing import Any, Callable, cast, List, Optional, Set, Tuple, Union
-from . import constants
-from .attachment import Attachment, BrokenAttachment, UnsupportedAttachment
-from .enums import (
- AttachErrorBehavior, ErrorBehavior, Importance, Priority,
- PropertiesType, Sensitivity, SideEffect
+from .. import constants
+from ..attachments import (
+ AttachmentBase, initStandardAttachment, SignedAttachment
+ )
+from ..encoding import lookupCodePage
+from ..enums import (
+ AttachErrorBehavior, ErrorBehavior, InsecureFeatures, Importance,
+ Priority, PropertiesType, SaveType, Sensitivity, SideEffect
)
-from .exceptions import (
- InvalidFileFormatError, StandardViolationError, UnrecognizedMSGTypeError
+from ..exceptions import (
+ ConversionError, InvalidFileFormatError, PrefixError,
+ StandardViolationError
)
-from .named import Named, NamedProperties
-from .prop import FixedLengthProp
-from .properties import Properties
-from .utils import (
- divide, getEncodingName, hasLen, inputToMsgPath, inputToString,
- msgPathToString, parseType, properHex, verifyPropertyId, verifyType,
- windowsUnicode
+from ..properties.named import Named, NamedProperties
+from ..properties.prop import FixedLengthProp
+from ..properties.properties_store import PropertiesStore
+from ..utils import (
+ divide, hasLen, inputToMsgPath, makeWeakRef, msgPathToString,
+ parseType, properHex, verifyPropertyId, verifyType, windowsUnicode
)
@@ -51,31 +59,35 @@ def __init__(self, path, **kwargs):
one. Do not set manually unless you know what you are doing.
:param parentMsg: Used for synchronizing named properties instances. Do
not set this unless you know what you are doing.
- :param attachmentClass: Optional, the class the MSGFile object will use
- for attachments. You probably should not change this value unless
- you know what you are doing.
+ :param initAttachment: Optional, the method used when creating an
+ attachment for an MSG file. MUST be a function that takes 2
+ arguments (the MSGFile instance and the directory in the MSG file
+ where the attachment is) and returns an instance of AttachmentBase.
:param delayAttachments: Optional, delays the initialization of
attachments until the user attempts to retrieve them. Allows MSG
files with bad attachments to be initialized so the other data can
be retrieved.
:param filename: Optional, the filename to be used by default when
saving.
- :param errorBehavior: Optional, the behavior to use in the event of an
- certain types of errors.
+ :param errorBehavior: Optional, the behavior to use in the event of
+ certain types of errors. Uses the ErrorBehavior enum.
:param overrideEncoding: Optional, an encoding to use instead of the one
specified by the msg file. Do not report encoding errors caused by
this.
:param treePath: Internal variable used for giving representation of the
path, as a tuple of objects, of the MSGFile. When passing, this is
the path to the parent object of this instance.
+ :param insecureFeatures: Optional, an enum value that specifies if
+ certain insecure features should be enabled. These features should
+ only be used on data that you trust. Uses the InsecureFeatures enum.
:raises InvalidFileFormatError: If the file is not an OleFile or could
not be parsed as an MSG file.
- :raises StandardViolationError: If some part of the file badly violates
+ :raises StandardViolationError: If some part of the file badly violates
the standard.
:raises IOError: If there is an issue opening the MSG file.
:raises NameError: If the encoding provided is not supported.
- :raises TypeError: If the prefix is not a supported type.
+ :raises PrefixError: If the prefix is not a supported type.
:raises TypeError: If the parent is not an instance of MSGFile or a
subclass.
:raises ValueError: If the attachment error behavior is not valid.
@@ -84,18 +96,19 @@ def __init__(self, path, **kwargs):
specific exceptions was raised.
"""
# Retrieve all the kwargs that we need.
+ self.__inscFeat = kwargs.get('insecureFeatures', InsecureFeatures.NONE)
prefix = kwargs.get('prefix', '')
- self.__parentMsg = kwargs.get('parentMsg')
- self.__treePath = kwargs.get('treePath', tuple()) + (self,)
+ self.__parentMsg = makeWeakRef(cast(MSGFile, kwargs.get('parentMsg')))
+ self.__treePath = kwargs.get('treePath', []) + [makeWeakRef(self)]
# Verify it is a valid class.
- if self.__parentMsg is not None and not isinstance(self.__parentMsg, MSGFile):
+ if self.__parentMsg and not isinstance(self.__parentMsg(), MSGFile):
raise TypeError(':param parentMsg: must be an instance of MSGFile or a subclass.')
filename = kwargs.get('filename', None)
overrideEncoding = kwargs.get('overrideEncoding', None)
# WARNING DO NOT MANUALLY MODIFY PREFIX. Let the program set it.
self.__path = path
- self.__attachmentClass = kwargs.get('attachmentClass', Attachment)
+ self.__initAttachmentFunc = kwargs.get('initAttachment', initStandardAttachment)
self.__attachmentsDelayed = kwargs.get('delayAttachments', False)
self.__attachmentsReady = False
self.__errorBehavior = ErrorBehavior(kwargs.get('errorBehavior', ErrorBehavior.THROW))
@@ -108,7 +121,6 @@ def __init__(self, path, **kwargs):
# necessary.
self.__errorBehavior = AttachErrorBehavior(kwargs['attachmentErrorBehavior'])
- self.__waitingProperties = []
if overrideEncoding is not None:
codecs.lookup(overrideEncoding)
logger.warning('You have chosen to override the string encoding. Do not report encoding errors caused by this.')
@@ -117,142 +129,93 @@ def __init__(self, path, **kwargs):
self.__listDirRes = {}
- # This is a variable that tells whether we own the olefile. Used for
- # closing.
- self.__oleOwner = True
if self.__parentMsg:
# We should be able to directly access the private variables of
# another instance with no issue.
- self.__ole = self.__parentMsg.__ole
+ self.__ole = self.__parentMsg().__ole
self.__oleOwner = False
else:
+ # Verify the path at least evaluates to True, as not doing so can
+ # allow an OleFile to be created without a path.
+ if not path:
+ raise ValueError(':param path: must be set and must not be empty.')
try:
- self.__ole = olefile.OleFileIO(path)
+ if ErrorBehavior.OLE_DEFECT_INCORRECT in self.errorBehavior:
+ defect = olefile.DEFECT_FATAL
+ else:
+ defect = olefile.DEFECT_INCORRECT
+ self.__ole = olefile.OleFileIO(path, raise_defects = defect)
except OSError as e:
logger.error(e)
if str(e) == 'not an OLE2 structured storage file':
raise InvalidFileFormatError(e)
else:
raise
+ # This is a variable that tells whether we own the olefile. Used for
+ # closing. We set it here for error handling.
+ self.__oleOwner = True
- kwargsCopy = copy.copy(kwargs)
- if 'prefix' in kwargsCopy:
- del kwargsCopy['prefix']
- if 'parentMsg' in kwargsCopy:
- del kwargsCopy['parentMsg']
- if 'filename' in kwargsCopy:
- del kwargsCopy['filename']
- if 'treePath' in kwargsCopy:
- del kwargsCopy['treePath']
- self.__kwargs = kwargsCopy
-
- prefixl = []
- if prefix:
- try:
- prefix = inputToString(prefix, 'utf-8')
- except Exception:
+ # The rest *must* be in a try-except block to ensure we close the file.
+ try:
+ kwargsCopy = copy.copy(kwargs)
+ if 'prefix' in kwargsCopy:
+ del kwargsCopy['prefix']
+ if 'parentMsg' in kwargsCopy:
+ del kwargsCopy['parentMsg']
+ if 'filename' in kwargsCopy:
+ del kwargsCopy['filename']
+ if 'treePath' in kwargsCopy:
+ del kwargsCopy['treePath']
+ self.__kwargs = kwargsCopy
+
+ prefixl = []
+ if prefix:
try:
- prefix = '/'.join(prefix)
- except Exception:
- raise TypeError(f'Invalid prefix type: {type(prefix)}\n' +
- '(This was probably caused by you setting it manually).')
- prefix = prefix.replace('\\', '/')
- g = prefix.split('/')
- if g[-1] == '':
- g.pop()
- prefixl = g
- if prefix[-1] != '/':
- prefix += '/'
- self.__prefix = prefix
- self.__prefixList = prefixl
- self.__prefixLen = len(prefixl)
- if prefix and not filename:
- filename = self._getStringStream(prefixl[:-1] + ['__substg1.0_3001'], prefix = False)
- if filename:
- self.filename = filename
- elif hasLen(path):
- if len(path) < 1536:
+ prefixl = inputToMsgPath(prefix)
+ prefix = '/'.join(prefixl) + '/'
+ except ConversionError:
+ raise PrefixError(f'The provided prefix could not be used: {prefix}')
+ self.__prefix = prefix
+ self.__prefixList = prefixl
+ self.__prefixLen = len(prefixl)
+ if prefix and not filename:
+ filename = self._getStringStream(prefixl[:-1] + ['__substg1.0_3001'], prefix = False)
+ if filename:
+ self.filename = filename
+ elif hasLen(path):
+ if len(path) < 1536:
+ self.filename = str(path)
+ else:
+ self.filename = None
+ elif isinstance(path, pathlib.Path):
self.filename = str(path)
else:
self.filename = None
- elif isinstance(path, pathlib.Path):
- self.filename = str(path)
- else:
- self.filename = None
- self.__open = True
+ self.__open = True
- # Now, load the attachments if we are not delaying them.
- if not self.__attachmentsDelayed:
- self.attachments
+ # Now, load the attachments if we are not delaying them.
+ if not self.__attachmentsDelayed:
+ self.attachments
+ except:
+ # *Any* exception here requires that we close the file.
+ try:
+ self.close()
+ except:
+ pass
+ # Raise the exception after trying to close the file.
+ raise
- def __enter__(self):
+ def __enter__(self) -> MSGFile:
self.__ole.__enter__()
return self
- def __exit__(self, *args, **kwargs):
+ def __exit__(self, *_) -> None:
self.close()
- def _ensureSet(self, variable : str, streamID, stringStream : bool = True, **kwargs):
- """
- Ensures that the variable exists, otherwise will set it using the
- specified stream. After that, return said variable.
-
- If the specified stream is not a string stream, make sure to set
- :param stringStream: to False.
-
- :param overrideClass: Class/function to use to morph the data that was
- read. The data will be the first argument to the class's __init__
- function or the function itself, if that is what is provided. By
- default, this will be completely ignored if the value was not found.
- :param preserveNone: If true (default), causes the function to ignore
- :param overrideClass: when the value could not be found (is None).
- If this is changed to False, then the value will be used regardless.
- """
- try:
- return getattr(self, variable)
- except AttributeError:
- if stringStream:
- value = self._getStringStream(streamID)
- else:
- value = self._getStream(streamID)
- # Check if we should be overriding the data type for this instance.
- if kwargs:
- overrideClass = kwargs.get('overrideClass')
- if overrideClass is not None and (value is not None or not kwargs.get('preserveNone', True)):
- value = overrideClass(value)
- setattr(self, variable, value)
- return value
-
- def _ensureSetNamed(self, variable : str, propertyName : str, guid : str, **kwargs):
- """
- Ensures that the variable exists, otherwise will set it using the named
- property. After that, return said variable.
-
- :param overrideClass: Class/function to use to morph the data that was
- read. The data will be the first argument to the class's __init__
- function or the function itself, if that is what is provided. By
- default, this will be completely ignored if the value was not found.
- :param preserveNone: If true (default), causes the function to ignore
- :param overrideClass: when the value could not be found (is None).
- If this is changed to False, then the value will be used regardless.
- """
- try:
- return getattr(self, variable)
- except AttributeError:
- value = self.namedProperties.get((propertyName, guid))
- # Check if we should be overriding the data type for this instance.
- if kwargs:
- overrideClass = kwargs.get('overrideClass')
- if overrideClass is not None and (value is not None or not kwargs.get('preserveNone', True)):
- value = overrideClass(value)
- setattr(self, variable, value)
- return value
-
- def _ensureSetProperty(self, variable : str, propertyName, **kwargs):
+ def _getNamedAs(self, propertyName : str, guid : str, overrideClass = None, preserveNone : bool = True):
"""
- Ensures that the variable exists, otherwise will set it using the
- property. After that, return said variable.
+ Returns the named property, setting the class if specified.
:param overrideClass: Class/function to use to morph the data that was
read. The data will be the first argument to the class's __init__
@@ -262,46 +225,13 @@ def _ensureSetProperty(self, variable : str, propertyName, **kwargs):
:param overrideClass: when the value could not be found (is None).
If this is changed to False, then the value will be used regardless.
"""
- try:
- return getattr(self, variable)
- except AttributeError:
- try:
- value = self.props[propertyName].value
- except (KeyError, AttributeError):
- value = None
- # Check if we should be overriding the data type for this instance.
- if kwargs:
- overrideClass = kwargs.get('overrideClass')
- if overrideClass is not None and (value is not None or not kwargs.get('preserveNone', True)):
- value = overrideClass(value)
- setattr(self, variable, value)
- return value
-
- def _ensureSetTyped(self, variable : str, _id, **kwargs):
- """
- Like the other ensure set functions, but designed for when something
- could be multiple types (where only one will be present). This way you
- have no need to set the type, it will be handled for you.
+ value = self.namedProperties.get((propertyName, guid))
+ # Check if we should be overriding the data type for this instance.
+ if overrideClass is not None:
+ if value is not None or not preserveNone:
+ value = overrideClass(value)
- :param overrideClass: Class/function to use to morph the data that was
- read. The data will be the first argument to the class's __init__
- function or the function itself, if that is what is provided. By
- default, this will be completely ignored if the value was not found.
- :param preserveNone: If true (default), causes the function to ignore
- :param overrideClass: when the value could not be found (is None).
- If this is changed to False, then the value will be used regardless.
- """
- try:
- return getattr(self, variable)
- except AttributeError:
- value = self._getTypedData(_id)
- # Check if we should be overriding the data type for this instance.
- if kwargs:
- overrideClass = kwargs.get('overrideClass')
- if overrideClass is not None and (value is not None or not kwargs.get('preserveNone', True)):
- value = overrideClass(value)
- setattr(self, variable, value)
- return value
+ return value
def _getOleEntry(self, filename, prefix : bool = True) -> olefile.olefile.OleDirectoryEntry:
"""
@@ -319,6 +249,29 @@ def _getOleEntry(self, filename, prefix : bool = True) -> olefile.olefile.OleDir
return self.__ole.direntries[sid]
+ def _getPropertyAs(self, propertyName, overrideClass = None, preserveNone : bool = True):
+ """
+ Returns the property, setting the class if specified.
+
+ :param overrideClass: Class/function to use to morph the data that was
+ read. The data will be the first argument to the class's __init__
+ function or the function itself, if that is what is provided. By
+ default, this will be completely ignored if the value was not found.
+ :param preserveNone: If True (default), causes the function to ignore
+ :param overrideClass: when the value could not be found (is None).
+ If this is changed to False, then the value will be used regardless.
+ """
+ try:
+ value = self.props[propertyName].value
+ except (KeyError, AttributeError):
+ value = None
+ # Check if we should be overriding the data type for this instance.
+ if overrideClass is not None:
+ if (value is not None or not preserveNone):
+ value = overrideClass(value)
+
+ return value
+
def _getStream(self, filename, prefix : bool = True) -> Optional[bytes]:
"""
Gets a binary representation of the requested filename.
@@ -334,6 +287,33 @@ def _getStream(self, filename, prefix : bool = True) -> Optional[bytes]:
logger.info(f'Stream "{filename}" was requested but could not be found. Returning `None`.')
return None
+ def _getStreamAs(self, streamID, stringStream : bool = True, overrideClass = None, preserveNone : bool = True):
+ """
+ Returns the specified stream, modifying it to the class if specified.
+
+ If the specified stream is not a string stream, make sure to set
+ :param stringStream: to False.
+
+ :param overrideClass: Class/function to use to morph the data that was
+ read. The data will be the first argument to the class's __init__
+ function or the function itself, if that is what is provided. By
+ default, this will be completely ignored if the value was not found.
+ :param preserveNone: If true (default), causes the function to ignore
+ :param overrideClass: when the value could not be found (is None).
+ If this is changed to False, then the value will be used regardless.
+ """
+ if stringStream:
+ value = self._getStringStream(streamID)
+ else:
+ value = self._getStream(streamID)
+
+ # Check if we should be overriding the data type for this instance.
+ if overrideClass is not None:
+ if value is not None or not preserveNone:
+ value = overrideClass(value)
+
+ return value
+
def _getStringStream(self, filename, prefix : bool = True) -> Optional[str]:
"""
Gets a string representation of the requested filename.
@@ -352,6 +332,28 @@ def _getStringStream(self, filename, prefix : bool = True) -> Optional[str]:
tmp = self._getStream(filename + '001E', prefix = False)
return None if tmp is None else tmp.decode(self.stringEncoding)
+ def _getTypedAs(self, _id : str, overrideClass = None, preserveNone : bool = True):
+ """
+ Like the other get as functions, but designed for when something
+ could be multiple types (where only one will be present). This way you
+ have no need to set the type, it will be handled for you.
+
+ :param overrideClass: Class/function to use to morph the data that was
+ read. The data will be the first argument to the class's __init__
+ function or the function itself, if that is what is provided. By
+ default, this will be completely ignored if the value was not found.
+ :param preserveNone: If true (default), causes the function to ignore
+ :param overrideClass: when the value could not be found (is None).
+ If this is changed to False, then the value will be used regardless.
+ """
+ value = self._getTypedData(_id)
+ # Check if we should be overriding the data type for this instance.
+ if overrideClass is not None:
+ if value is not None or not preserveNone:
+ value = overrideClass(value)
+
+ return value
+
def _getTypedData(self, _id : str, _type = None, prefix : bool = True):
"""
Gets the data for the specified id as the type that it is supposed to
@@ -370,7 +372,7 @@ def _getTypedData(self, _id : str, _type = None, prefix : bool = True):
found, result = self._getTypedProperty(_id, _type)
return result if found else None
- def _getTypedProperty(self, propertyID : str, _type = None):
+ def _getTypedProperty(self, propertyID : str, _type = None) -> Tuple[bool, Optional[object]]:
"""
Gets the property with the specified id as the type that it is supposed
to be. :param id: MUST be a 4 digit hexadecimal string.
@@ -454,14 +456,11 @@ def _oleListDir(self, streams : bool = True, storages : bool = False) -> List:
def close(self) -> None:
if self.__open:
- try:
- # If this throws an AttributeError then we have not loaded the attachments.
- self._attachments
+ if self.attachmentsReady:
for attachment in self.attachments:
if attachment.type == 'msg':
attachment.data.close()
- except AttributeError:
- pass
+
if self.__oleOwner:
self.__ole.close()
@@ -534,7 +533,7 @@ def export(self, path) -> None:
:param path: An IO device with a write method which accepts bytes or a
path-like object (including strings and pathlib.Path objects).
"""
- from .ole_writer import OleWriter
+ from ..ole_writer import OleWriter
# Create an instance of the class used for writing a new OLE file.
writer = OleWriter()
@@ -594,7 +593,10 @@ def slistDir(self, streams : bool = True, storages : bool = False) -> List[str]:
"""
return [msgPathToString(x) for x in self.listDir(streams, storages)]
- def save(self, *args, **kwargs):
+ def save(self, **kwargs) -> constants.SAVE_TYPE:
+ if kwargs.get('skipNotImplemented', False):
+ return (SaveType.NONE, None)
+
raise NotImplementedError(f'Saving is not yet supported for the {self.__class__.__name__} class.')
def saveAttachments(self, **kwargs) -> None:
@@ -641,72 +643,36 @@ def saveRaw(self, path):
if data is not None:
f.write(data)
- @property
+ @functools.cached_property
def areStringsUnicode(self) -> bool:
"""
Returns a boolean telling if the strings are unicode encoded.
"""
- try:
- return self.__bStringsUnicode
- except AttributeError:
- if '340D0003' in self.props:
- if (self.props['340D0003'].value & 0x40000) != 0:
- self.__bStringsUnicode = True
- return self.__bStringsUnicode
- self.__bStringsUnicode = False
- return self.__bStringsUnicode
+ if '340D0003' in self.props:
+ if (self.props['340D0003'].value & 0x40000) != 0:
+ return True
- @property
- def attachments(self) -> List:
+ return False
+
+ @functools.cached_property
+ def attachments(self) -> Union[List[AttachmentBase], List[SignedAttachment]]:
"""
Returns a list of all attachments.
"""
- try:
- return self._attachments
- except AttributeError:
- # Get the attachments.
- attachmentDirs = []
- prefixLen = self.prefixLen
- for dir_ in self.listDir(False, True):
- if dir_[prefixLen].startswith('__attach') and \
- dir_[prefixLen] not in attachmentDirs:
- attachmentDirs.append(dir_[prefixLen])
+ # Get the attachments.
+ attachmentDirs = []
+ for dir_ in self.listDir(False, True, False):
+ if dir_[0].startswith('__attach') and dir_[0] not in attachmentDirs:
+ attachmentDirs.append(dir_[0])
- self._attachments = []
-
- for attachmentDir in attachmentDirs:
- try:
- self._attachments.append(self.attachmentClass(self, attachmentDir))
- except (NotImplementedError, UnrecognizedMSGTypeError) as e:
- if self.errorBehavior & ErrorBehavior.ATTACH_NOT_IMPLEMENTED:
- logger.exception(f'Error processing attachment at {attachmentDir}')
- self._attachments.append(UnsupportedAttachment(self, attachmentDir))
- else:
- raise
- except StandardViolationError as e:
- if self.errorBehavior & ErrorBehavior.STANDARDS_VIOLATION:
- logger.exception(f'Unresolvable standards violation in {attachmentDir}')
- self._attachments.append(BrokenAttachment(self, attachmentDir))
- else:
- raise
- except Exception as e:
- if self.errorBehavior & ErrorBehavior.ATTACH_BROKEN:
- logger.exception(f'Error processing attachment at {attachmentDir}')
- self._attachments.append(BrokenAttachment(self, attachmentDir))
- else:
- raise
+ attachments = []
- self.__attachmentsReady = True
+ for attachmentDir in attachmentDirs:
+ attachments.append(self.initAttachmentFunc(self, attachmentDir))
- return self._attachments
+ self.__attachmentsReady = True
- @property
- def attachmentClass(self):
- """
- Returns the Attachment class being used, should you need to use it
- externally for whatever reason.
- """
- return self.__attachmentClass
+ return attachments
@property
def attachmentsDelayed(self) -> bool:
@@ -722,64 +688,64 @@ def attachmentsReady(self) -> bool:
"""
return self.__attachmentsReady
- @property
+ @functools.cached_property
def classified(self) -> bool:
"""
Indicates whether the contents of this message are regarded as
classified information.
"""
- return self._ensureSetNamed('_classified', '85B5', constants.PSETID_COMMON, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('85B5', constants.ps.PSETID_COMMON, overrideClass = bool, preserveNone = False)
- @property
+ @functools.cached_property
def classType(self) -> Optional[str]:
"""
The class type of the MSG file.
"""
- return self._ensureSet('_classType', '__substg1.0_001A')
+ return self._getStringStream('__substg1.0_001A')
- @property
+ @functools.cached_property
def commonEnd(self) -> Optional[datetime.datetime]:
"""
The end time for the object.
"""
- return self._ensureSetNamed('_commonEnd', '8517', constants.PSETID_COMMON)
+ return self._getNamedAs('8517', constants.ps.PSETID_COMMON)
- @property
+ @functools.cached_property
def commonStart(self) -> Optional[datetime.datetime]:
"""
The start time for the object.
"""
- return self._ensureSetNamed('_commonStart', '8516', constants.PSETID_COMMON)
+ return self._getNamedAs('8516', constants.ps.PSETID_COMMON)
- @property
+ @functools.cached_property
def currentVersion(self) -> Optional[int]:
"""
Specifies the build number of the client application that sent the
message.
"""
- return self._ensureSetNamed('_currentVersion', '8552', constants.PSETID_COMMON)
+ return self._getNamedAs('8552', constants.ps.PSETID_COMMON)
- @property
+ @functools.cached_property
def currentVersionName(self) -> Optional[str]:
"""
Specifies the name of the client application that sent the message.
"""
- return self._ensureSetNamed('_currentVersionName', '8554', constants.PSETID_COMMON)
-
+ return self._getNamedAs('8554', constants.ps.PSETID_COMMON)
+
@property
def errorBehavior(self) -> ErrorBehavior:
"""
- The behavior to follow when an attachment raises an exception. Will be
- a member of the ErrorBehavior enum.
+ The behavior to follow when certain errors occur. Will be an instance of
+ the ErrorBehavior enum.
"""
return self.__errorBehavior
- @property
+ @functools.cached_property
def importance(self) -> Optional[Importance]:
"""
The specified importance of the msg file.
"""
- return self._ensureSetProperty('_importance', '00170003', overrideClass = Importance)
+ return self._getPropertyAs('00170003', Importance)
@property
def importanceString(self) -> Union[str, None]:
@@ -790,10 +756,26 @@ def importanceString(self) -> Union[str, None]:
return {
Importance.HIGH: 'High',
Importance.MEDIUM: None,
- Importance.LOW: 'low',
+ Importance.LOW: 'Low',
None: None,
}[self.importance]
+ @property
+ def initAttachmentFunc(self) -> Callable[[MSGFile, Any], AttachmentBase]:
+ """
+ Returns the method for initializing attachments being used, should you
+ need to use it externally for whatever reason.
+ """
+ return self.__initAttachmentFunc
+
+ @property
+ def insecureFeatures(self) -> InsecureFeatures:
+ """
+ An enum specifying what insecure features have been enabled for this
+ file.
+ """
+ return self.__inscFeat
+
@property
def kwargs(self) -> dict:
"""
@@ -802,39 +784,32 @@ def kwargs(self) -> dict:
"""
return self.__kwargs
- @property
+ @functools.cached_property
def named(self) -> Named:
"""
The main named properties storage. This is not usable to access the data
of the properties directly.
+
+ :raises ReferenceError: The parent MSGFile instance has been garbage
+ collected.
"""
- try:
- return self.__named
- except AttributeError:
- self.__named = None
- # Handle the parent msg file existing.
- if self.__parentMsg:
- # Try to get the named properties and use that for our main
- # instance.
- try:
- self.__named = self.__parentMsg.named
- except Exception:
- pass
- if not self.__named:
- self.__named = Named(self)
- return self.__named
+ # Handle the parent msg file existing.
+ if self.__parentMsg:
+ # Try to get the named properties and use that for our main
+ # instance.
+ if (msg := self.__parentMsg()) is None:
+ raise ReferenceError('Parent MSGFile instance has been garbage collected.')
+ return msg.named
+ else:
+ return Named(self)
- @property
+ @functools.cached_property
def namedProperties(self) -> NamedProperties:
"""
The NamedProperties instances usable to access the data for named
properties.
"""
- try:
- return self.__namedProperties
- except AttributeError:
- self.__namedProperties = NamedProperties(self.named, self)
- return self.__namedProperties
+ return NamedProperties(self.named, self)
@property
def overrideEncoding(self):
@@ -867,53 +842,49 @@ def prefixLen(self) -> int:
return self.__prefixLen
@property
- def prefixList(self):
+ def prefixList(self) -> List[str]:
"""
Returns the prefix list of the Message instance. Intended for developer
use.
"""
return copy.deepcopy(self.__prefixList)
- @property
+ @functools.cached_property
def priority(self) -> Optional[Priority]:
"""
The specified priority of the msg file.
"""
- return self._ensureSetProperty('_priority', '00260003', overrideClass = Priority)
+ return self._getPropertyAs('00260003', Priority)
- @property
- def props(self) -> Properties:
+ @functools.cached_property
+ def props(self) -> PropertiesStore:
"""
Returns the Properties instance used by the MSGFile instance.
"""
- try:
- return self._prop
- except AttributeError:
- if not (stream := self._getStream('__properties_version1.0')):
- if self.__errorBehavior & ErrorBehavior.STANDARDS_VIOLATION:
- logger.error('File does not contain a property stream.')
- else:
- # Raise the exception from None so we don't get all the "during
- # the handling of the above exception" stuff.
- raise StandardViolationError('File does not contain a property stream.') from None
- self._prop = Properties(stream,
- PropertiesType.MESSAGE if self.prefix == '' else PropertiesType.MESSAGE_EMBED)
- return self._prop
+ if not (stream := self._getStream('__properties_version1.0')):
+ if ErrorBehavior.STANDARDS_VIOLATION in self.__errorBehavior:
+ logger.error('File does not contain a property stream.')
+ else:
+ # Raise the exception from None so we don't get all the "during
+ # the handling of the above exception" stuff.
+ raise StandardViolationError('File does not contain a property stream.') from None
+ return PropertiesStore(stream,
+ PropertiesType.MESSAGE if self.prefix == '' else PropertiesType.MESSAGE_EMBED)
- @property
+ @functools.cached_property
def sensitivity(self) -> Optional[Sensitivity]:
"""
The specified sensitivity of the msg file.
"""
- return self._ensureSetProperty('_sensitivity', '00360003', overrideClass = Sensitivity)
+ return self._getPropertyAs('00360003', Sensitivity)
- @property
- def sideEffects(self) -> Optional[Set[SideEffect]]:
+ @functools.cached_property
+ def sideEffects(self) -> Optional[SideEffect]:
"""
Controls how a Message object is handled by the client in relation to
certain user interface actions by the user, such as deleting a message.
"""
- return self._ensureSetNamed('_sideEffects', '8510', constants.PSETID_COMMON, overrideClass = SideEffect.fromBits)
+ return self._getNamedAs('8510', constants.ps.PSETID_COMMON, SideEffect)
@property
def stringEncoding(self):
@@ -935,13 +906,15 @@ def stringEncoding(self):
else:
enc = self.props['3FFD0003'].value
# Now we just need to translate that value.
- self.__stringEncoding = getEncodingName(enc)
+ self.__stringEncoding = lookupCodePage(enc)
return self.__stringEncoding
@property
- def treePath(self) -> Tuple:
+ def treePath(self) -> List[weakref.ReferenceType]:
"""
- A path, as a tuple of instances, needed to get to this instance through
- the MSGFile-Attachment tree.
+ A path, as a list of weak reference to the instances needed to get to
+ this instance through the MSGFile-Attachment tree. These are weak
+ references to ensure the garbage collector doesn't see the references
+ back to higher objects.
"""
return self.__treePath
diff --git a/extract_msg/post.py b/extract_msg/msg_classes/post.py
similarity index 93%
rename from extract_msg/post.py
rename to extract_msg/msg_classes/post.py
index 8b87727d..8167c260 100644
--- a/extract_msg/post.py
+++ b/extract_msg/msg_classes/post.py
@@ -3,13 +3,14 @@
]
+import functools
import json
from typing import Optional
-from . import constants
+from .. import constants
from .message_base import MessageBase
-from .utils import inputToString
+from ..utils import inputToString
from imapclient.imapclient import decode_utf7
@@ -31,12 +32,12 @@ def getJson(self) -> str:
'body': decode_utf7(self.body),
})
- @property
+ @functools.cached_property
def conversation(self) -> Optional[str]:
"""
The name of the conversation being posted to.
"""
- return self._ensureSet('_convo', '__substg1.0_0070')
+ return self._getStringStream('__substg1.0_0070')
@property
def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
diff --git a/extract_msg/msg_classes/sticky_note.py b/extract_msg/msg_classes/sticky_note.py
new file mode 100644
index 00000000..ca9eaa00
--- /dev/null
+++ b/extract_msg/msg_classes/sticky_note.py
@@ -0,0 +1,56 @@
+import functools
+
+from typing import Optional
+
+from .. import constants
+from ..enums import NoteColor
+from .message_base import MessageBase
+
+
+# Note: Sticky note is basically just text and a background color, so we don't
+# really do much when saving it.
+class StickyNote(MessageBase):
+ """
+ A sticky note.
+ """
+
+ @property
+ def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
+ return None
+
+ @functools.cached_property
+ def noteColor(self) -> Optional[NoteColor]:
+ """
+ The color of the sticky note.
+ """
+ return self._getNamedAs('8B00', constants.ps.PSETID_NOTE, NoteColor)
+
+ @functools.cached_property
+ def noteHeight(self) -> Optional[int]:
+ """
+ The height of the note window, in pixels.
+ """
+ return self._getNamedAs('8B03', constants.ps.PSETID_NOTE)
+
+ @functools.cached_property
+ def noteWidth(self) -> Optional[int]:
+ """
+ The width of the note window, in pixels.
+ """
+ return self._getNamedAs('8B02', constants.ps.PSETID_NOTE)
+
+ @functools.cached_property
+ def noteX(self) -> Optional[int]:
+ """
+ The distance, in pixels, from the left edge of the screen that a user
+ interface displays the note.
+ """
+ return self._getNamedAs('8B02', constants.ps.PSETID_NOTE)
+
+ @functools.cached_property
+ def noteY(self) -> Optional[int]:
+ """
+ The distance, in pixels, from the top edge of the screen that a user
+ interafce displays the note.
+ """
+ return self._getNamedAs('8B02', constants.ps.PSETID_NOTE)
\ No newline at end of file
diff --git a/extract_msg/task.py b/extract_msg/msg_classes/task.py
similarity index 65%
rename from extract_msg/task.py
rename to extract_msg/msg_classes/task.py
index e99c14b8..71533955 100644
--- a/extract_msg/task.py
+++ b/extract_msg/msg_classes/task.py
@@ -4,18 +4,19 @@
import datetime
+import functools
import logging
-from typing import Optional, Set
+from typing import Optional
-from . import constants
-from .enums import (
+from .. import constants
+from ..enums import (
TaskAcceptance, TaskHistory, TaskMode, TaskMultipleRecipients,
TaskOwnership, TaskState, TaskStatus
)
from .message_base import MessageBase
-from .structures.recurrence_pattern import RecurrencePattern
-from .utils import unsignedToSignedInt
+from ..structures.recurrence_pattern import RecurrencePattern
+from ..utils import unsignedToSignedInt
logger = logging.getLogger(__name__)
@@ -81,45 +82,45 @@ def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
},
}
- @property
+ @functools.cached_property
def percentComplete(self) -> Optional[float]:
"""
Indicates whether a time-flagged Message object is complete. Returns a
percentage in decimal form. 1.0 indicates it is complete.
"""
- return self._ensureSetNamed('_percentComplete', '8102', constants.PSETID_TASK)
+ return self._getNamedAs('8102', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskAcceptanceState(self) -> Optional[TaskAcceptance]:
"""
Indicates the acceptance state of the task.
"""
- return self._ensureSetNamed('_taskAcceptanceState', '812A', constants.PSETID_TASK, overrideClass = TaskAcceptance)
+ return self._getNamedAs('812A', constants.ps.PSETID_TASK, TaskAcceptance)
- @property
+ @functools.cached_property
def taskAccepted(self) -> bool:
"""
Indicates whether a task assignee has replied to a tesk request for this
task object. Does not indicate if it was accepted or rejected.
"""
- return self._ensureSetNamed('_taskAccepted', '8108', constants.PSETID_TASK, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8108', constants.ps.PSETID_TASK, bool, False)
- @property
+ @functools.cached_property
def taskActualEffort(self) -> Optional[int]:
"""
Indicates the number of minutes that the user actually spent working on
a task.
"""
- return self._ensureSetNamed('_taskActualEffort', '8110', constants.PSETID_TASK)
+ return self._getNamedAs('8110', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskAssigner(self) -> Optional[str]:
"""
Specifies the name of the user that last assigned the task.
"""
- return self._ensureSetNamed('_taskAssigner', '8121', constants.PSETID_TASK)
+ return self._getNamedAs('8121', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskAssigners(self) -> Optional[bytes]:
"""
A stack of entries, each representing a task assigner. The most recent
@@ -127,229 +128,229 @@ def taskAssigners(self) -> Optional[bytes]:
The documentation on this is weird, so I don't know how to parse it.
"""
- return self._ensureSetNamed('_taskAssigners', '8117', constants.PSETID_TASK)
+ return self._getNamedAs('8117', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskComplete(self) -> bool:
"""
Indicates if the task is complete.
"""
- return self._ensureSetNamed('_taskComplete', '811C', constants.PSETID_TASK, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('811C', constants.ps.PSETID_TASK, bool, False)
- @property
+ @functools.cached_property
def taskCustomFlags(self) -> Optional[int]:
"""
Custom flags set on the task.
"""
- return self._ensureSetNamed('_taskCustomFlags', '8139', constants.PSETID_TASK)
+ return self._getNamedAs('8139', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskDateCompleted(self) -> Optional[datetime.datetime]:
"""
The date when the user completed work on the task.
"""
- return self._ensureSetNamed('_taskDateCompleted', '810F', constants.PSETID_TASK)
+ return self._getNamedAs('810F', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskDeadOccurrence(self) -> bool:
"""
Indicates whether a new recurring task remains to be generated. Set to
False on a new Task object and True when the client generates the last
recurring task.
"""
- return self._ensureSetNamed('_taskDeadOccurrence', '8109', constants.PSETID_TASK, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8109', constants.ps.PSETID_TASK, bool, False)
- @property
+ @functools.cached_property
def taskDueDate(self) -> Optional[datetime.datetime]:
"""
Specifies the date by which the user expects work on the task to be
complete.
"""
- return self._ensureSetNamed('_taskDueDate', '8105', constants.PSETID_TASK)
+ return self._getNamedAs('8105', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskEstimatedEffort(self) -> Optional[int]:
"""
Indicates the number of minutes that the user expects to work on a task.
"""
- return self._ensureSetNamed('_taskEstimatedEffort', '8111', constants.PSETID_TASK)
+ return self._getNamedAs('8111', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskFCreator(self) -> bool:
"""
Indicates that the task object was originally created by the action of
the current user or user agent instead of by the processing of a task
request.
"""
- return self._ensureSetNamed('_taskFCreator', '811E', constants.PSETID_TASK, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('811E', constants.ps.PSETID_TASK, bool, False)
- @property
+ @functools.cached_property
def taskFFixOffline(self) -> bool:
"""
Indicates whether the value of the taskOwner property is correct.
"""
- return self._ensureSetNamed('taskFFixOffline', '812C', constants.PSETID_TASK, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('812C', constants.ps.PSETID_TASK, bool, False)
- @property
+ @functools.cached_property
def taskFRecurring(self) -> bool:
"""
Indicates whether the task includes a recurrence pattern.
"""
- return self._ensureSetNamed('_taskFRecurring', '8126', constants.PSETID_TASK, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8126', constants.ps.PSETID_TASK, bool, False)
- @property
+ @functools.cached_property
def taskGlobalID(self) -> Optional[bytes]:
"""
Specifies a unique GUID for this task, used to locate an existing task
upon receipt of a task response or task update.
"""
- return self._ensureSetNamed('_taskGlobalID', '8519', constants.PSETID_COMMON)
+ return self._getNamedAs('8519', constants.ps.PSETID_COMMON)
- @property
+ @functools.cached_property
def taskHistory(self) -> Optional[TaskHistory]:
"""
Indicates the type of change that was last made to the Task object.
"""
- return self._ensureSetNamed('_taskHistory', '811A', constants.PSETID_TASK, overrideClass = TaskHistory)
+ return self._getNamedAs('811A', constants.ps.PSETID_TASK, TaskHistory)
- @property
+ @functools.cached_property
def taskLastDelegate(self) -> Optional[str]:
"""
Contains the name of the user who most recently assigned the task, or
the user to whom it was most recently assigned.
"""
- return self._ensureSetNamed('_taskLastDelegate', '8125', constants.PSETID_TASK)
+ return self._getNamedAs('8125', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskLastUpdate(self) -> Optional[datetime.datetime]:
"""
The date and time of the most recent change made to the task object.
"""
- return self._ensureSetNamed('_taskLastUpdate', '8115', constants.PSETID_TASK)
+ return self._getNamedAs('8115', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskLastUser(self) -> Optional[str]:
"""
Contains the name of the most recent user to have been the owner of the
task.
"""
- return self._ensureSetNamed('_taskLastUser', '8122', constants.PSETID_TASK)
+ return self._getNamedAs('8122', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskMode(self) -> Optional[TaskMode]:
"""
Used in a task communication. Should be 0 (UNASSIGNED) on task objects.
"""
- return self._ensureSetNamed('_taskMode', '8518', constants.PSETID_COMMON, overrideClass = TaskMode)
+ return self._getNamedAs('8518', constants.ps.PSETID_COMMON, TaskMode)
- @property
- def taskMultipleRecipients(self) -> Optional[Set[TaskMultipleRecipients]]:
+ @functools.cached_property
+ def taskMultipleRecipients(self) -> Optional[TaskMultipleRecipients]:
"""
- Returns a set of flags that specify optimization hints about the
+ Returns a union of flags that specify optimization hints about the
recipients of a Task object.
"""
- return self._ensureSetNamed('_taskMultipleRecipients', '8120', constants.PSETID_TASK, overrideClass = TaskMultipleRecipients.fromBits)
+ return self._getNamedAs('8120', constants.ps.PSETID_TASK, TaskMultipleRecipients)
- @property
+ @functools.cached_property
def taskNoCompute(self) -> Optional[bool]:
"""
This value is not used and has no impact on a Task, but is provided for
completeness.
"""
- return self._ensureSetNamed('_taskNoCompute', '8124', constants.PSETID_TASK)
+ return self._getNamedAs('8124', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskOrdinal(self) -> Optional[int]:
"""
Specifies a number that aids custom sorting of Task objects.
"""
- return self._ensureSetNamed('_taskOrdinal', '8123', constants.PSETID_TASK, overrideClass = unsignedToSignedInt)
+ return self._getNamedAs('8123', constants.ps.PSETID_TASK, unsignedToSignedInt)
- @property
+ @functools.cached_property
def taskOwner(self) -> Optional[str]:
"""
Contains the name of the owner of the task.
"""
- return self._ensureSetNamed('_taskOwner', '811F', constants.PSETID_TASK)
+ return self._getNamedAs('811F', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskOwnership(self) -> Optional[TaskOwnership]:
"""
Contains the name of the owner of the task.
"""
- return self._ensureSetNamed('_taskOwnership', '8129', constants.PSETID_TASK, overrideClass = TaskOwnership)
+ return self._getNamedAs('8129', constants.ps.PSETID_TASK, TaskOwnership)
- @property
+ @functools.cached_property
def taskRecurrence(self) -> Optional[RecurrencePattern]:
"""
Contains a RecurrencePattern structure that provides information about
recurring tasks.
"""
- return self._ensureSetNamed('_taskRecurrence', '8116', constants.PSETID_TASK, overrideClass = RecurrencePattern)
+ return self._getNamedAs('8116', constants.ps.PSETID_TASK, RecurrencePattern)
- @property
+ @functools.cached_property
def taskResetReminder(self) -> bool:
"""
Indicates whether future recurring tasks need reminders.
"""
- return self._ensureSetNamed('_taskResetReminder', '8107', constants.PSETID_TASK, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8107', constants.ps.PSETID_TASK, bool, False)
- @property
+ @functools.cached_property
def taskRole(self) -> Optional[str]:
"""
This value is not used and has no impact on a Task, but is provided for
completeness.
"""
- return self._ensureSetNamed('_taskRole', '8127', constants.PSETID_TASK)
+ return self._getNamedAs('8127', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskStartDate(self) -> Optional[datetime.datetime]:
"""
Specifies the date on which the user expects work on the task to begin.
"""
- return self._ensureSetNamed('_taskStartDate', '8104', constants.PSETID_TASK)
+ return self._getNamedAs('8104', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def taskState(self) -> Optional[TaskState]:
"""
Indicates the current assignment state of the Task object.
"""
- return self._ensureSetNamed('_taskState', '8113', constants.PSETID_TASK, overrideClass = TaskState)
+ return self._getNamedAs('8113', constants.ps.PSETID_TASK, TaskState)
- @property
+ @functools.cached_property
def taskStatus(self) -> Optional[TaskStatus]:
"""
The completion status of a task.
"""
- return self._ensureSetNamed('_taskStatus', '8101', constants.PSETID_TASK, overrideClass = TaskStatus)
+ return self._getNamedAs('8101', constants.ps.PSETID_TASK, TaskStatus)
- @property
+ @functools.cached_property
def taskStatusOnComplete(self) -> bool:
"""
Indicates whether the task assignee has been requested to send an email
message upon completion of the assigned task.
"""
- return self._ensureSetNamed('_taskStatusOnComplete', '8119', constants.PSETID_TASK, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('8119', constants.ps.PSETID_TASK, bool, False)
- @property
+ @functools.cached_property
def taskUpdates(self) -> bool:
"""
Indicates whether the task assignee has been requested to send a task
update when the assigned Task object changes.
"""
- return self._ensureSetNamed('_taskUpdates', '811B', constants.PSETID_TASK, overrideClass = bool, preserveNone = False)
+ return self._getNamedAs('811B', constants.ps.PSETID_TASK, bool, False)
- @property
+ @functools.cached_property
def taskVersion(self) -> Optional[int]:
"""
Indicates which copy is the latest update of a Task object.
"""
- return self._ensureSetNamed('_taskVersion', '8112', constants.PSETID_TASK)
+ return self._getNamedAs('8112', constants.ps.PSETID_TASK)
- @property
+ @functools.cached_property
def teamTask(self) -> Optional[bool]:
"""
This value is not used and has no impact on a Task, but is provided for
completeness.
"""
- return self._ensureSetNamed('_teamTask', '8103', constants.PSETID_TASK)
+ return self._getNamedAs('8103', constants.ps.PSETID_TASK)
diff --git a/extract_msg/task_request.py b/extract_msg/msg_classes/task_request.py
similarity index 62%
rename from extract_msg/task_request.py
rename to extract_msg/msg_classes/task_request.py
index 6b2ea8cf..0a19c9c1 100644
--- a/extract_msg/task_request.py
+++ b/extract_msg/msg_classes/task_request.py
@@ -3,13 +3,14 @@
]
+import functools
import logging
from typing import Optional
-from . import constants
-from .enums import ErrorBehavior, TaskMode, TaskRequestType
-from .exceptions import StandardViolationError
+from .. import constants
+from ..enums import ErrorBehavior, TaskMode, TaskRequestType
+from ..exceptions import StandardViolationError
from .message_base import MessageBase
from .task import Task
@@ -53,23 +54,23 @@ def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
# print. So I guess we just return None and handle that.
return None
- @property
+ @functools.cached_property
def processed(self) -> bool:
"""
Indicates whether a client has already processed a received task
communication.
"""
- return self._ensureSetProperty('_processed', '7D01000B', overrideClass = bool, preserveNone = False)
+ return self._getPropertyAs('7D01000B', bool, False)
- @property
+ @functools.cached_property
def taskMode(self) -> Optional[TaskMode]:
"""
The assignment status of the embedded Task object.
"""
- return self._ensureSetNamed('_taskMode', '8518', constants.PSETID_COMMON, overrideClass = TaskMode)
+ return self._getNamedAs('8518', constants.ps.PSETID_COMMON, TaskMode)
- @property
- def taskObject(self) -> Task:
+ @functools.cached_property
+ def taskObject(self) -> Optional[Task]:
"""
The task object embedded in this Task Request object.
@@ -79,33 +80,31 @@ def taskObject(self) -> Task:
:raises StandardViolationError: A standard was blatently violated in a
way that program does not tolerate.
"""
- try:
- return self._taskObject
- except AttributeError:
- # Get the task object.
- #
- # The task object MUST be the first attachment, but we will be
- # lenient and allow it to be in any position. It not existing,
- # however, will not be tolerated.
- task = next(((index, att) for index, att in self.attachments if isinstance(att.data, Task)), None)
- if task is None:
- if self.errorBehavior & ErrorBehavior.STANDARDS_VIOLATION:
- logger.error('Task object not found on TaskRequest object.')
- return
- raise StandardViolationError('Task object not found on TaskRequest object.')
-
- # We know we have the task, let's make sure it's at index 0. If not,
- # log it.
- if task[0] != 0:
- logger.warning('Embedded task object was not located at index 0.')
-
- self._taskObject = task[1]
-
- return self._taskObject
+ # Get the task object.
+ #
+ # The task object MUST be the first attachment, but we will be
+ # lenient and allow it to be in any position. It not existing,
+ # however, will not be tolerated.
+ task = next(((index, att) for index, att in self.attachments if isinstance(att.data, Task)), None)
- @property
+ if task is None:
+ if ErrorBehavior.STANDARDS_VIOLATION in self.errorBehavior:
+ logger.error('Task object not found on TaskRequest object.')
+ return
+ raise StandardViolationError('Task object not found on TaskRequest object.')
+
+ # We know we have the task, let's make sure it's at index 0. If not,
+ # log it.
+ if task[0] != 0:
+ logger.warning('Embedded task object was not located at index 0.')
+
+ self._taskObject = task[1]
+
+ return self._taskObject
+
+ @functools.cached_property
def taskRequestType(self) -> TaskRequestType:
"""
The type of task request.
"""
- return self._ensureSet('_taskRequestType', '__substg1.0_001A', TaskRequestType.fromClassType)
+ return self._getStreamAs('__substg1.0_001A', TaskRequestType.fromClassType)
diff --git a/extract_msg/ole_writer.py b/extract_msg/ole_writer.py
index 8642ca6b..7158d865 100644
--- a/extract_msg/ole_writer.py
+++ b/extract_msg/ole_writer.py
@@ -21,8 +21,7 @@
# Allow for nice type checking.
if TYPE_CHECKING:
- from .msg import MSGFile
-
+ from .msg_classes import MSGFile
class DirectoryEntry:
@@ -70,7 +69,7 @@ def toBytes(self):
nameBytes = self.name.encode('utf-16-le')
- return constants.ST_CF_DIR_ENTRY.pack(
+ return constants.st.ST_CF_DIR_ENTRY.pack(
nameBytes,
len(nameBytes) + 2,
self.type,
@@ -436,25 +435,25 @@ def _writeBeginning(self, f) -> int:
# Reserved.
f.write(b'\x00\x00\x00\x00\x00\x00')
# Number of directory sectors. Version 3 says this *must* be 0.
- f.write(constants.ST_LE_UI32.pack(0))
+ f.write(constants.st.ST_LE_UI32.pack(0))
# Number of FAT sectors.
- f.write(constants.ST_LE_UI32.pack(numFat))
+ f.write(constants.st.ST_LE_UI32.pack(numFat))
# First directory sector location (Sector for the directory stream).
# We place that right after the DIFAT and FAT.
- f.write(constants.ST_LE_UI32.pack(numFat + numDifat))
+ f.write(constants.st.ST_LE_UI32.pack(numFat + numDifat))
# Transation signature number.
f.write(b'\x00\x00\x00\x00')
# Mini stream cutoff size.
f.write(b'\x00\x10\x00\x00')
# First mini FAT sector location.
- f.write(constants.ST_LE_UI32.pack((numFat + numDifat + ceilDiv(self.__dirEntryCount, 4)) if self.__numMinifat > 0 else 0xFFFFFFFE))
+ f.write(constants.st.ST_LE_UI32.pack((numFat + numDifat + ceilDiv(self.__dirEntryCount, 4)) if self.__numMinifat > 0 else 0xFFFFFFFE))
# Number of mini FAT sectors.
- f.write(constants.ST_LE_UI32.pack(ceilDiv(self.__numMinifatSectors, 128)))
+ f.write(constants.st.ST_LE_UI32.pack(ceilDiv(self.__numMinifatSectors, 128)))
# First DIFAT sector location. If there are none, set to 0xFFFFFFFE (End
# of chain).
- f.write(constants.ST_LE_UI32.pack(0 if numDifat else 0xFFFFFFFE))
+ f.write(constants.st.ST_LE_UI32.pack(0 if numDifat else 0xFFFFFFFE))
# Number of DIFAT sectors.
- f.write(constants.ST_LE_UI32.pack(numDifat))
+ f.write(constants.st.ST_LE_UI32.pack(numDifat))
# To make life easier on me, I'm having the code start with the DIFAT
# followed by the FAT sectors, as I can write them all at once before
@@ -465,9 +464,9 @@ def _writeBeginning(self, f) -> int:
# This kind of sucks to code, ngl.
if x > 109 and (x - 109) % 127 == 0:
# If we are at the end of a DIFAT sector, write the jump.
- f.write(constants.ST_LE_UI32.pack((x - 109) // 127))
+ f.write(constants.st.ST_LE_UI32.pack((x - 109) // 127))
# Write the next FAT sector location.
- f.write(constants.ST_LE_UI32.pack(x + numDifat))
+ f.write(constants.st.ST_LE_UI32.pack(x + numDifat))
# Finally, fill out the last DIFAT sector with null entries.
if numFat > 109:
@@ -489,7 +488,7 @@ def _writeBeginning(self, f) -> int:
# Fill in the values for the directory stream.
for x in range(offset + 1, offset + ceilDiv(self.__dirEntryCount, 4)):
- f.write(constants.ST_LE_UI32.pack(x))
+ f.write(constants.st.ST_LE_UI32.pack(x))
# Write the end of chain marker.
f.write(b'\xFE\xFF\xFF\xFF')
@@ -500,7 +499,7 @@ def _writeBeginning(self, f) -> int:
if self.__numMinifatSectors > 0:
# Mini FAT chain.
for x in range(offset + 1, offset + ceilDiv(self.__numMinifat, 16)):
- f.write(constants.ST_LE_UI32.pack(x))
+ f.write(constants.st.ST_LE_UI32.pack(x))
# Write the end of chain marker.
f.write(b'\xFE\xFF\xFF\xFF')
@@ -509,7 +508,7 @@ def _writeBeginning(self, f) -> int:
# The mini stream sectors.
for x in range(offset + 1, offset + self.__numMinifat):
- f.write(constants.ST_LE_UI32.pack(x))
+ f.write(constants.st.ST_LE_UI32.pack(x))
# Write the end of chain marker.
f.write(b'\xFE\xFF\xFF\xFF')
@@ -524,7 +523,7 @@ def _writeBeginning(self, f) -> int:
size = ceilDiv(len(entry.data), 512)
entry.startingSectorLocation = offset
for x in range(offset + 1, offset + size):
- f.write(constants.ST_LE_UI32.pack(x))
+ f.write(constants.st.ST_LE_UI32.pack(x))
# Write the end of chain marker.
f.write(b'\xFE\xFF\xFF\xFF')
@@ -577,7 +576,7 @@ def _writeMini(self, f, entries : List[DirectoryEntry]) -> None:
if x.type == DirectoryEntryType.STREAM and len(x.data) < 4096:
size = ceilDiv(len(x.data), 64)
for x in range(currentSector + 1, currentSector + size):
- f.write(constants.ST_LE_UI32.pack(x))
+ f.write(constants.st.ST_LE_UI32.pack(x))
if size > 0:
f.write(b'\xFE\xFF\xFF\xFF')
currentSector += size
@@ -879,7 +878,7 @@ def renameEntry(self, path, newName : str) -> None:
# First, validate the new name.
if not isinstance(newName, str):
raise ValueError('New name must be a string.')
- if constants.RE_INVALID_OLE_PATH.search(newName):
+ if constants.re.INVALID_OLE_PATH.search(newName):
raise ValueError('Invalid character(s) in new name. Must not contain the following characters: \\//!:')
if len(newName) > 31:
raise ValueError('New name must be less than 32 characters.')
diff --git a/extract_msg/open_msg.py b/extract_msg/open_msg.py
new file mode 100644
index 00000000..439fa1a0
--- /dev/null
+++ b/extract_msg/open_msg.py
@@ -0,0 +1,197 @@
+from __future__ import annotations
+
+
+__all__ = [
+ 'openMsg',
+ 'openMsgBulk',
+]
+
+
+import glob
+import logging
+
+from typing import List, Optional, Tuple, TYPE_CHECKING, Union
+
+from . import constants
+from .exceptions import (
+ InvalidFileFormatError, UnrecognizedMSGTypeError,
+ UnsupportedMSGTypeError
+ )
+
+
+logger = logging.getLogger(__name__)
+logger.addHandler(logging.NullHandler())
+
+if TYPE_CHECKING:
+ from .msg_classes import MSGFile
+
+
+def _getMsgClassInfo(classType : str) -> Tuple[bool, Optional[str]]:
+ """
+ Checks if the specified class type is recognized by the module. Usually used
+ for checking if a type is simply unsupported rather than unknown.
+
+ Returns a tuple of two items. The first is whether it is known. If it is
+ known and support is refused, the second item will be a string of the
+ relevent issue number. Otherwise, it will be None.
+ """
+ classType = classType.lower()
+ if classType == 'ipm':
+ return (True, None)
+
+ for item in constants.KNOWN_CLASS_TYPES:
+ if classType.startswith(item):
+ # Check if the found class type has had support refused.
+ for tup in constants.REFUSED_CLASS_TYPES:
+ if tup[0] == item:
+ return (True, tup[1])
+ else:
+ return (True, None)
+
+ return (False, None)
+
+
+def openMsg(path, **kwargs) -> MSGFile:
+ """
+ Function to automatically open an MSG file and detect what type it is.
+
+ Accepts all of the same arguments as the __init__ method for the class it
+ creates. Extra options will be ignored if the class doesn't know what to do
+ with them, but child instances may end up using them if they understand
+ them. See :method MSGFile.__init__: for a list of all globally recognized
+ options.
+
+ If :param strict: is set to True, this function will raise an exception when
+ it cannot identify what MSGFile derivitive to use. Otherwise, it will log
+ the error and return a basic MSGFile instance. Default is True.
+
+ :raises UnsupportedMSGTypeError: if the type is recognized but not suppoted.
+ :raises UnrecognizedMSGTypeError: if the type is not recognized.
+ """
+ from .msg_classes import (
+ AppointmentMeeting, Contact, MeetingCancellation, MeetingException,
+ MeetingForwardNotification, MeetingRequest, MeetingResponse,
+ Message, MSGFile, MessageSigned, Post, StickyNote, Task, TaskRequest
+ )
+
+ # When the initial MSG file is opened, it should *always* delay attachments
+ # so it can get the main class type. We only need to load them after that
+ # if we are directly returning the MSGFile instance *and* delayAttachments
+ # is False.
+ #
+ # So first let's store the original value.
+ delayAttachments = kwargs.get('delayAttachments', False)
+ kwargs['delayAttachments'] = True
+
+ msg = MSGFile(path, **kwargs)
+
+ # Restore the option in the kwargs so we don't have to worry about it.
+ kwargs['delayAttachments'] = delayAttachments
+
+ # After rechecking the docs, all comparisons should be case-insensitive, not
+ # case-sensitive. My reading ability is great.
+ #
+ # Also after consideration, I realized we need to be very careful here, as
+ # other file types (like doc, ppt, etc.) might open but not return a class
+ # type. If the stream is not found, classType returns None, which has no
+ # lower function. So let's make sure we got a good return first.
+ if not msg.classType:
+ if kwargs.get('strict', True):
+ raise InvalidFileFormatError('File was confirmed to be an olefile, but was not an MSG file.')
+ else:
+ # If strict mode is off, we'll just return an MSGFile anyways.
+ logger.critical('Received file that was an olefile but was not an MSG file. Returning MSGFile anyways because strict mode is off.')
+ return msg
+ classType = msg.classType.lower()
+ # Put the message class first as it is most common.
+ if classType.startswith('ipm.note') or classType.startswith('report'):
+ msg.close()
+ if classType.endswith('smime.multipartsigned') or classType.endswith('smime'):
+ return MessageSigned(path, **kwargs)
+ else:
+ return Message(path, **kwargs)
+ elif classType.startswith('ipm.appointment'):
+ msg.close()
+ return AppointmentMeeting(path, **kwargs)
+ elif classType.startswith('ipm.contact') or classType.startswith('ipm.distlist'):
+ msg.close()
+ return Contact(path, **kwargs)
+ elif classType.startswith('ipm.post'):
+ msg.close()
+ return Post(path, **kwargs)
+ elif classType.startswith('ipm.schedule.meeting.request'):
+ msg.close()
+ return MeetingRequest(path, **kwargs)
+ elif classType.startswith('ipm.schedule.meeting.canceled'):
+ msg.close()
+ return MeetingCancellation(path, **kwargs)
+ elif classType.startswith('ipm.schedule.meeting.notification.forward'):
+ msg.close()
+ return MeetingForwardNotification(path, **kwargs)
+ elif classType.startswith('ipm.schedule.meeting.resp'):
+ msg.close()
+ return MeetingResponse(path, **kwargs)
+ elif classType.startswith('ipm.stickynote'):
+ msg.close()
+ return StickyNote(path, **kwargs)
+ elif classType.startswith('ipm.taskrequest'):
+ msg.close()
+ return TaskRequest(path, **kwargs)
+ elif classType.startswith('ipm.task'):
+ msg.close()
+ return Task(path, **kwargs)
+ elif classType.startswith('ipm.ole.class.{00061055-0000-0000-c000-000000000046}'):
+ # Exception objects have a weird class type.
+ msg.close()
+ return MeetingException(path, **kwargs)
+ elif classType == 'ipm':
+ # Unspecified format. It should be equal to this and not just start with
+ # it.
+ if not delayAttachments:
+ msg.attachments
+ return msg
+ elif kwargs.get('strict', True):
+ # Because we are closing it, we need to store it in a variable first.
+ ct = msg.classType
+ msg.close()
+ # Now we need to figure out exactly what we are going to be reporting to
+ # the user.
+ if (info := _getMsgClassInfo(classType))[0]:
+ if info[1]:
+ raise UnsupportedMSGTypeError(f'Support for MSG type "{ct}" has been refused. See {constants.REPOSITORY_URL}/issues/{info[1]} for more information.')
+ raise UnsupportedMSGTypeError(f'MSG type "{ct}" currently is not supported by the module. If you would like support, please make a feature request.')
+ raise UnrecognizedMSGTypeError(f'Could not recognize MSG class type "{ct}". As such, there is a high chance that support may be impossible, but you should contact the developers to find out more.')
+ else:
+ if (info := _getMsgClassInfo(classType))[0]:
+ if info[1]:
+ logger.error(f'Support for MSG type "{msg.classType}" has been refused. See {constants.REPOSITORY_URL}/issues/{info[1]} for more information.')
+ else:
+ logger.error(f'MSG type "{msg.classType}" currently is not supported by the module. If you would like support, please make a feature request.')
+ logger.error(f'Could not recognize MSG class type "{msg.classType}". As such, there is a high chance that support may be impossible, but you should contact the developers to find out more.')
+ if not delayAttachments:
+ msg.attachments
+ return msg
+
+
+def openMsgBulk(path, **kwargs) -> Union[List[MSGFile], Tuple[Exception, Union[str, bytes]]]:
+ """
+ Takes the same arguments as openMsg, but opens a collection of msg files
+ based on a wild card. Returns a list if successful, otherwise returns a
+ tuple.
+
+ :param ignoreFailures: If this is True, will return a list of all successful
+ files, ignoring any failures. Otherwise, will close all that
+ successfully opened, and return a tuple of the exception and the path of
+ the file that failed.
+ """
+ files = []
+ for x in glob.glob(str(path)):
+ try:
+ files.append(openMsg(x, **kwargs))
+ except Exception as e:
+ if not kwargs.get('ignoreFailures', False):
+ for msg in files:
+ msg.close()
+ return (e, x)
+
+ return files
\ No newline at end of file
diff --git a/extract_msg/properties/__init__.py b/extract_msg/properties/__init__.py
new file mode 100644
index 00000000..0a56ac61
--- /dev/null
+++ b/extract_msg/properties/__init__.py
@@ -0,0 +1,23 @@
+"""
+Classes and functions involved with managing properties.
+"""
+
+__all__ = [
+ 'FixedLengthProp',
+ 'Named',
+ 'NamedProperties',
+ 'NamedPropertyBase',
+ 'NumericalNamedProperty',
+ 'PropBase',
+ 'PropertiesStore',
+ 'StringNamedProperty',
+ 'VariableLengthProp',
+]
+
+
+from .named import (
+ Named, NamedProperties, NamedPropertyBase, NumericalNamedProperty,
+ StringNamedProperty
+ )
+from .prop import FixedLengthProp, PropBase, VariableLengthProp
+from .properties_store import PropertiesStore
\ No newline at end of file
diff --git a/extract_msg/named.py b/extract_msg/properties/named.py
similarity index 72%
rename from extract_msg/named.py
rename to extract_msg/properties/named.py
index 61008b68..d7431ed4 100644
--- a/extract_msg/named.py
+++ b/extract_msg/properties/named.py
@@ -14,26 +14,32 @@
import logging
import pprint
-from typing import Dict, Optional, TYPE_CHECKING
+from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING, Union
-from . import constants
-from .enums import NamedPropertyType
-from .utils import bytesToGuid, divide, properHex
+from .. import constants
+from ..enums import NamedPropertyType
+from ..utils import bytesToGuid, divide, makeWeakRef, properHex
from compressed_rtf.crc32 import crc32
# Allow for nice type checking.
if TYPE_CHECKING:
- from .msg import MSGFile
+ from ..msg_classes.msg import MSGFile
+ from ..attachments.attachment_base import AttachmentBase
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
class Named:
+ """
+ Class for handling access to the named properties themselves.
+ """
+
__dir = '__nameid_version1.0'
- def __init__(self, msg):
- self.__msg = msg
+
+ def __init__(self, msg : MSGFile):
+ self.__msg = makeWeakRef(msg)
# Get the basic streams. If all are emtpy, then nothing to do.
guidStream = self._getStream('__substg1.0_00020102') or self._getStream('__substg1.0_00020102', False)
entryStream = self._getStream('__substg1.0_00030102') or self._getStream('__substg1.0_00030102', False)
@@ -51,10 +57,10 @@ def __init__(self, msg):
# Check that we even have any entries. If there are none, nothing to do.
if entryStream:
- guids = tuple([None, constants.PS_MAPI, constants.PS_PUBLIC_STRINGS] + [bytesToGuid(x) for x in divide(guidStream, 16)])
- entries = []
+ guids = tuple([None, constants.ps.PS_MAPI, constants.ps.PS_PUBLIC_STRINGS] + [bytesToGuid(x) for x in divide(guidStream, 16)])
+ entries : List[Dict[str, Any]]= []
for rawStream in divide(entryStream, 8):
- tmp = constants.STNP_ENT.unpack(rawStream)
+ tmp = constants.st.STNP_ENT.unpack(rawStream)
entry = {
'id': tmp[0],
'pid': tmp[2],
@@ -78,8 +84,18 @@ def __init__(self, msg):
def __contains__(self, key) -> bool:
return key in self.__propertiesDict
- def __getitem__(self, key):
- return self.__propertiesDict[key]
+ def __getitem__(self, propertyName : Tuple[str, str]):
+ # Validate the key.
+ if not hasattr(propertyName, '__len__') or len(propertyName) != 2:
+ raise TypeError('Named property key must be a tuple of two strings.')
+
+ # Case insensitive search of the dictionary.
+ propertyName = (propertyName[0].upper(), propertyName[1].upper())
+ for key in self.__propertiesDict.keys():
+ if propertyName == (key[0].upper(), key[1].upper()):
+ return self.__propertiesDict[key]
+
+ raise KeyError(propertyName)
def __iter__(self):
return self.__propertiesDict.__iter__()
@@ -103,7 +119,7 @@ def __getName(self, offset : int) -> str:
raise ValueError('Failed to parse named property: offset was not in string stream.')
# Get the length, in bytes, of the string.
- length = constants.STNP_NAM.unpack(self.namesStream[offset:offset + 4])[0]
+ length = constants.st.STNP_NAM.unpack(self.namesStream[offset:offset + 4])[0]
offset += 4
# Make sure the string can be read entirely. If it can't, something was
@@ -114,29 +130,58 @@ def __getName(self, offset : int) -> str:
return self.namesStream[offset:offset + length].decode('utf-16-le')
def _getStream(self, filename, prefix = True) -> Optional[bytes]:
- return self.__msg._getStream([self.__dir, filename], prefix = prefix)
+ """
+ Gets a binary representation of the requested filename.
+
+ This should ALWAYS return a bytes object if it was found, otherwise
+ returns None.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
+ """
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Named instance has been garbage collected.')
+ return msg._getStream([self.__dir, filename], prefix = prefix)
def _getStringStream(self, filename, prefix = True) -> Optional[str]:
"""
Gets a string representation of the requested filename.
- Checks for both ASCII and Unicode representations and returns
- a value if possible. If there are both ASCII and Unicode
- versions, then :param prefer: specifies which will be
- returned.
+
+ Rather than the full filename, you should only feed this function the
+ filename sans the type. So if the full name is "__substg1.0_001A001F",
+ the filename this function should receive should be "__substg1.0_001A".
+
+ This should ALWAYS return a string if it was found, otherwise returns
+ None.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
"""
- return self.__msg._getStringStream([self.__dir, filename], prefix = prefix)
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Named instance has been garbage collected.')
+ return msg._getStringStream([self.__dir, filename], prefix = prefix)
def exists(self, filename) -> bool:
"""
Checks if stream exists inside the named properties folder.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
"""
- return self.__msg.exists([self.__dir, filename])
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Named instance has been garbage collected.')
+ return msg.exists([self.__dir, filename])
def sExists(self, filename) -> bool:
"""
Checks if the string stream exists inside the named properties folder.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
"""
- return self.__msg.sExists([self.__dir, filename])
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Named instance has been garbage collected.')
+ return msg.sExists([self.__dir, filename])
def get(self, propertyName, default = None):
"""
@@ -144,12 +189,8 @@ def get(self, propertyName, default = None):
if not found. Key is a tuple of the name and the property set GUID.
"""
try:
- return self.__propertiesDict[propertyName]
+ return self[propertyName]
except KeyError:
- propertyName = propertyName.upper()
- for key in self.__propertiesDict.keys():
- if propertyName == key.upper():
- return self.__propertiesDict[key]
return default
def keys(self):
@@ -175,8 +216,13 @@ def dir(self):
def msg(self) -> MSGFile:
"""
Returns the Message instance the attachment belongs to.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
"""
- return self.__msg
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Named instance has been garbage collected.')
+ return msg
@property
def namedProperties(self) -> Dict:
@@ -192,7 +238,7 @@ class NamedProperties:
An instance that uses a Named instance and an extract-msg class to read the
data of named properties.
"""
- def __init__(self, named, streamSource):
+ def __init__(self, named, streamSource : Union[MSGFile, AttachmentBase]):
"""
:param named: The named instance to refer to for named properties
entries.
@@ -200,22 +246,30 @@ def __init__(self, named, streamSource):
property.
"""
self.__named = named
- self.__streamSource = streamSource
+ self.__streamSource = makeWeakRef(streamSource)
def __getitem__(self, item):
"""
Get a named property using the [] operator. Item must be a named
property instance or a tuple with 2 items: the name and the GUID string.
+
+ :raises ReferenceError: The associated instance for getting actual
+ property data has been garbage collected.
"""
+ if (source := self.__streamSource()) is None:
+ raise ReferenceError('The stream source for the NamedProperties instance has been garbage collected.')
if isinstance(item, NamedPropertyBase):
- return self.__streamSource._getTypedData(item.propertyStreamID)
+ return source._getTypedData(item.propertyStreamID)
else:
- return self.__streamSource._getTypedData(self.__named[item].propertyStreamID)
+ return source._getTypedData(self.__named[item].propertyStreamID)
def get(self, item, default = None):
"""
Get a named property, returning the value of :param default: if not
found. Item must be a tuple with 2 items: the name and the GUID string.
+
+ :raises ReferenceError: The associated instance for getting actual
+ property data has been garbage collected.
"""
try:
return self[item]
@@ -295,7 +349,7 @@ def __init__(self, entry, name):
# generated by certain versions of Outlook. As such, a little bit of
# additional code will need to run to determine exactly what the stream
# ID should be if it is in that property set.
- if self.guid == constants.PS_INTERNET_HEADERS:
+ if self.guid == constants.ps.PS_INTERNET_HEADERS:
# To be sure if it needs to be lower the most effective method would
# be to just get the Stream ID and then check if the entry is in
# there. If it isn't, then check the regular case and see. If it is
diff --git a/extract_msg/prop.py b/extract_msg/properties/prop.py
similarity index 83%
rename from extract_msg/prop.py
rename to extract_msg/properties/prop.py
index e72b7ae1..c83fa7f4 100644
--- a/extract_msg/prop.py
+++ b/extract_msg/properties/prop.py
@@ -1,10 +1,13 @@
+from __future__ import annotations
+
+
__all__ = [
- # Classes.
+ # Classes:
'FixedLengthProp'
'PropBase',
'VariableLengthProp',
- # Functions.
+ # Functions:
'createProp',
]
@@ -14,22 +17,22 @@
from typing import Any
-from . import constants
-from .enums import ErrorCode, ErrorCodeType
-from .utils import filetimeToDatetime, properHex
+from .. import constants
+from ..enums import ErrorCode, ErrorCodeType
+from ..utils import filetimeToDatetime, properHex
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
-def createProp(data : bytes) -> 'PropBase':
- temp = constants.ST2.unpack(data)[0]
+def createProp(data : bytes) -> PropBase:
+ temp = constants.st.ST2.unpack(data)[0]
if temp in constants.FIXED_LENGTH_PROPS:
return FixedLengthProp(data)
else:
if temp not in constants.VARIABLE_LENGTH_PROPS:
- # DEBUG
+ # DEBUG.
logger.warning(f'Unknown property type: {properHex(temp)}')
return VariableLengthProp(data)
@@ -42,7 +45,7 @@ class PropBase:
def __init__(self, data : bytes):
self.__rawData = data
self.__name = properHex(data[3::-1]).upper()
- self.__type, self.__flags = constants.ST2.unpack(data)
+ self.__type, self.__flags = constants.st.ST2.unpack(data)
self.__fm = self.__flags & 1 == 1
self.__fr = self.__flags & 2 == 2
self.__fw = self.__flags & 4 == 4
@@ -97,6 +100,7 @@ def type(self) -> int:
return self.__type
+
class FixedLengthProp(PropBase):
"""
Class to contain the data for a single fixed length property.
@@ -106,7 +110,7 @@ class FixedLengthProp(PropBase):
def __init__(self, data : bytes):
super().__init__(data)
- self.__value = self.parseType(self.type, constants.STFIX.unpack(data)[0])
+ self.__value = self.parseType(self.type, constants.st.STFIX.unpack(data)[0])
def parseType(self, _type : int, stream : bytes) -> Any:
"""
@@ -126,20 +130,20 @@ def parseType(self, _type : int, stream : bytes) -> Any:
logger.warning('Property type is PtypNull, but is not equal to 0.')
value = None
elif _type == 0x0002: # PtypInteger16
- value = constants.STI16.unpack(value)[0]
+ value = constants.st.STI16.unpack(value)[0]
elif _type == 0x0003: # PtypInteger32
- value = constants.STI32.unpack(value)[0]
+ value = constants.st.STI32.unpack(value)[0]
elif _type == 0x0004: # PtypFloating32
- value = constants.STF32.unpack(value)[0]
+ value = constants.st.STF32.unpack(value)[0]
elif _type == 0x0005: # PtypFloating64
- value = constants.STF64.unpack(value)[0]
+ value = constants.st.STF64.unpack(value)[0]
elif _type == 0x0006: # PtypCurrency
- value = (constants.STI64.unpack(value))[0] / 10000.0
+ value = (constants.st.STI64.unpack(value))[0] / 10000.0
elif _type == 0x0007: # PtypFloatingTime
- value = constants.STF64.unpack(value)[0]
+ value = constants.st.STF64.unpack(value)[0]
return constants.PYTPFLOATINGTIME_START + datetime.timedelta(days = value)
elif _type == 0x000A: # PtypErrorCode
- value = constants.STI32.unpack(value)[0]
+ value = constants.st.STI32.unpack(value)[0]
try:
value = ErrorCodeType(value)
except ValueError:
@@ -152,11 +156,11 @@ def parseType(self, _type : int, stream : bytes) -> Any:
except ValueError:
pass
elif _type == 0x000B: # PtypBoolean
- value = constants.ST3.unpack(value)[0] == 1
+ value = constants.st.ST3.unpack(value)[0] == 1
elif _type == 0x0014: # PtypInteger64
- value = constants.STI64.unpack(value)[0]
+ value = constants.st.STI64.unpack(value)[0]
elif _type == 0x0040: # PtypTime
- rawTime = constants.ST3.unpack(value)[0]
+ rawTime = constants.st.ST3.unpack(value)[0]
try:
value = filetimeToDatetime(rawTime)
except ValueError as e:
@@ -175,6 +179,7 @@ def value(self) -> Any:
return self.__value
+
class VariableLengthProp(PropBase):
"""
Class to contain the data for a single variable length property.
@@ -182,7 +187,7 @@ class VariableLengthProp(PropBase):
def __init__(self, data : bytes):
super().__init__(data)
- self.__length, self.__reserved = constants.STVAR.unpack(data)
+ self.__length, self.__reserved = constants.st.STVAR.unpack(data)
if self.type == 0x001E:
self.__realLength = self.__length - 1
elif self.type == 0x001F:
diff --git a/extract_msg/properties.py b/extract_msg/properties/properties_store.py
similarity index 95%
rename from extract_msg/properties.py
rename to extract_msg/properties/properties_store.py
index 54055260..16244d35 100644
--- a/extract_msg/properties.py
+++ b/extract_msg/properties/properties_store.py
@@ -1,5 +1,5 @@
__all__ = [
- 'Properties',
+ 'PropertiesStore',
]
@@ -11,17 +11,17 @@
from typing import Any, Dict, Optional, Union
from warnings import warn
-from . import constants
-from .enums import Intelligence, PropertiesType
+from .. import constants
+from ..enums import Intelligence, PropertiesType
from .prop import createProp, PropBase
-from .utils import divide, properHex
+from ..utils import divide, properHex
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
-class Properties:
+class PropertiesStore:
"""
Parser for msg properties files.
"""
@@ -33,9 +33,8 @@ def __init__(self, data : Optional[bytes], _type : Optional[PropertiesType] = No
if not isinstance(data, bytes):
raise TypeError(':param data: MUST be bytes.')
self.__rawData = data
- self.__pos = 0
self.__len = len(data)
- self.__props = {}
+ self.__props : Dict[PropBase] = {}
self.__naid = None
self.__nrid = None
self.__ac = None
@@ -49,10 +48,10 @@ def __init__(self, data : Optional[bytes], _type : Optional[PropertiesType] = No
self.__intel = Intelligence.SMART
if _type == PropertiesType.MESSAGE:
skip = 32
- self.__nrid, self.__naid, self.__rc, self.__ac = constants.ST1.unpack(self.__rawData[:24])
+ self.__nrid, self.__naid, self.__rc, self.__ac = constants.st.ST1.unpack(self.__rawData[:24])
elif _type == PropertiesType.MESSAGE_EMBED:
skip = 24
- self.__nrid, self.__naid, self.__rc, self.__ac = constants.ST1.unpack(self.__rawData[:24])
+ self.__nrid, self.__naid, self.__rc, self.__ac = constants.st.ST1.unpack(self.__rawData[:24])
else:
skip = 8
else:
@@ -80,7 +79,7 @@ def __init__(self, data : Optional[bytes], _type : Optional[PropertiesType] = No
def __contains__(self, key) -> bool:
return self.__props.__contains__(key)
- def __getitem__(self, key):
+ def __getitem__(self, key) -> PropBase:
return self.__props.__getitem__(key)
def __iter__(self):
diff --git a/extract_msg/recipient.py b/extract_msg/recipient.py
index 7cb61478..66e54ce9 100644
--- a/extract_msg/recipient.py
+++ b/extract_msg/recipient.py
@@ -3,16 +3,17 @@
]
+import functools
import logging
-from typing import Optional, Union
+from typing import Optional, Tuple, Union
from .enums import ErrorBehavior, MeetingRecipientType, PropertiesType, RecipientType
from .exceptions import StandardViolationError
-from .prop import FixedLengthProp
-from .properties import Properties
+from .properties.prop import FixedLengthProp
+from .properties.properties_store import PropertiesStore
from .structures.entry_id import PermanentEntryID
-from .utils import verifyPropertyId, verifyType
+from .utils import makeWeakRef, verifyPropertyId, verifyType
logger = logging.getLogger(__name__)
@@ -21,65 +22,73 @@
class Recipient:
"""
- Contains the data of one of the recipients in an msg file.
+ Contains the data of one of the recipients in an MSG file.
"""
def __init__(self, _dir, msg):
- self.__msg = msg # Allows calls to original msg file.
+ self.__msg = makeWeakRef(msg) # Allows calls to original msg file.
self.__dir = _dir
if not self.exists('__properties_version1.0'):
- if msg.errorBehavior & ErrorBehavior.STANDARDS_VIOLATION:
+ if ErrorBehavior.STANDARDS_VIOLATION in msg.errorBehavior:
logger.error('Recipients MUST have a property stream.')
else:
raise StandardViolationError('Recipients MUST have a property stream.') from None
- self.__props = Properties(self._getStream('__properties_version1.0'), PropertiesType.RECIPIENT)
+ self.__props = PropertiesStore(self._getStream('__properties_version1.0'), PropertiesType.RECIPIENT)
self.__email = self._getStringStream('__substg1.0_39FE')
if not self.__email:
self.__email = self._getStringStream('__substg1.0_3003')
self.__name = self._getStringStream('__substg1.0_3001')
self.__typeFlags = self.__props.get('0C150003').value or 0
- from .calendar_base import CalendarBase
+ from .msg_classes.calendar_base import CalendarBase
if isinstance(msg, CalendarBase):
self.__type = MeetingRecipientType(0xF & self.__typeFlags)
else:
self.__type = RecipientType(0xF & self.__typeFlags)
self.__formatted = f'{self.__name} <{self.__email}>'
- def _ensureSet(self, variable, streamID, stringStream : bool = True, **kwargs):
+ def _getPropertyAs(self, propertyName, overrideClass = None, preserveNone : bool = True):
"""
- Ensures that the variable exists, otherwise will set it using the
- specified stream. After that, return said variable.
-
- If the specified stream is not a string stream, make sure to set
- :param string stream: to False.
+ Returns the property, setting the class if specified.
:param overrideClass: Class/function to use to morph the data that was
read. The data will be the first argument to the class's __init__
function or the function itself, if that is what is provided. By
default, this will be completely ignored if the value was not found.
- :param preserveNone: If true (default), causes the function to ignore
+ :param preserveNone: If True (default), causes the function to ignore
:param overrideClass: when the value could not be found (is None).
If this is changed to False, then the value will be used regardless.
"""
try:
- return getattr(self, variable)
- except AttributeError:
- if stringStream:
- value = self._getStringStream(streamID)
- else:
- value = self._getStream(streamID)
- # Check if we should be overriding the data type for this instance.
- if kwargs:
- overrideClass = kwargs.get('overrideClass')
- if overrideClass is not None and (value is not None or not kwargs.get('preserveNone', True)):
- value = overrideClass(value)
- setattr(self, variable, value)
- return value
+ value = self.props[propertyName].value
+ except (KeyError, AttributeError):
+ value = None
+ # Check if we should be overriding the data type for this instance.
+ if overrideClass is not None:
+ if (value is not None or not preserveNone):
+ value = overrideClass(value)
+
+ return value
+
+ def _getStream(self, filename) -> Optional[bytes]:
+ """
+ Gets a binary representation of the requested filename.
+
+ This should ALWAYS return a bytes object if it was found, otherwise
+ returns None.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
+ """
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Recipient instance has been garbage collected.')
+ return msg._getStream([self.__dir, filename])
- def _ensureSetProperty(self, variable : str, propertyName : str, **kwargs):
+ def _getStreamAs(self, streamID, stringStream : bool = True, overrideClass = None, preserveNone : bool = True):
"""
- Ensures that the variable exists, otherwise will set it using the
- property. After that, return said variable.
+ Returns the specified stream, modifying it to the class if specified.
+
+ If the specified stream is not a string stream, make sure to set
+ :param stringStream: to False.
:param overrideClass: Class/function to use to morph the data that was
read. The data will be the first argument to the class's __init__
@@ -89,24 +98,39 @@ def _ensureSetProperty(self, variable : str, propertyName : str, **kwargs):
:param overrideClass: when the value could not be found (is None).
If this is changed to False, then the value will be used regardless.
"""
- try:
- return getattr(self, variable)
- except AttributeError:
- try:
- value = self.props[propertyName].value
- except (KeyError, AttributeError):
- value = None
- # Check if we should be overriding the data type for this instance.
- if kwargs:
- overrideClass = kwargs.get('overrideClass')
- if overrideClass is not None and (value is not None or not kwargs.get('preserveNone', True)):
- value = overrideClass(value)
- setattr(self, variable, value)
- return value
-
- def _ensureSetTyped(self, variable : str, _id, **kwargs):
- """
- Like the other ensure set functions, but designed for when something
+ if stringStream:
+ value = self._getStringStream(streamID)
+ else:
+ value = self._getStream(streamID)
+
+ # Check if we should be overriding the data type for this instance.
+ if overrideClass is not None:
+ if value is not None or not preserveNone:
+ value = overrideClass(value)
+
+ return value
+
+ def _getStringStream(self, filename) -> Optional[str]:
+ """
+ Gets a string representation of the requested filename.
+
+ Rather than the full filename, you should only feed this function the
+ filename sans the type. So if the full name is "__substg1.0_001A001F",
+ the filename this function should receive should be "__substg1.0_001A".
+
+ This should ALWAYS return a string if it was found, otherwise returns
+ None.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
+ """
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Recipient instance has been garbage collected.')
+ return msg._getStringStream([self.__dir, filename])
+
+ def _getTypedAs(self, _id : str, overrideClass = None, preserveNone : bool = True):
+ """
+ Like the other get as functions, but designed for when something
could be multiple types (where only one will be present). This way you
have no need to set the type, it will be handled for you.
@@ -118,35 +142,13 @@ def _ensureSetTyped(self, variable : str, _id, **kwargs):
:param overrideClass: when the value could not be found (is None).
If this is changed to False, then the value will be used regardless.
"""
- try:
- return getattr(self, variable)
- except AttributeError:
- value = self._getTypedData(_id)
- # Check if we should be overriding the data type for this instance.
- if kwargs:
- overrideClass = kwargs.get('overrideClass')
- if overrideClass is not None and (value is not None or not kwargs.get('preserveNone', True)):
- value = overrideClass(value)
- setattr(self, variable, value)
- return value
-
- def _getStream(self, filename) -> Optional[bytes]:
- """
- Gets a binary representation of the requested filename.
+ value = self._getTypedData(_id)
+ # Check if we should be overriding the data type for this instance.
+ if overrideClass is not None:
+ if value is not None or not preserveNone:
+ value = overrideClass(value)
- This should ALWAYS return a bytes object if it was found, otherwise
- returns None.
- """
- return self.__msg._getStream([self.__dir, filename])
-
- def _getStringStream(self, filename) -> Optional[str]:
- """
- Gets a string representation of the requested filename. Checks for both
- Unicode and Non-Unicode representations and returns a value if possible.
- If there are both Unicode and Non-Unicode versions, then :param prefer:
- specifies which will be returned.
- """
- return self.__msg._getStringStream([self.__dir, filename])
+ return value
def _getTypedData(self, _id, _type = None):
"""
@@ -156,6 +158,9 @@ def _getTypedData(self, _id, _type = None):
If you know for sure what type the data is before hand, you can specify
it as being one of the strings in the constant FIXED_LENGTH_PROPS_STRING
or VARIABLE_LENGTH_PROPS_STRING.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
"""
verifyPropertyId(id)
_id = _id.upper()
@@ -166,7 +171,7 @@ def _getTypedData(self, _id, _type = None):
found, result = self._getTypedProperty(_id, _type)
return result if found else None
- def _getTypedProperty(self, propertyID : str, _type = None):
+ def _getTypedProperty(self, propertyID : str, _type = None) -> Tuple[bool, Optional[object]]:
"""
Gets the property with the specified id as the type that it is supposed
to be. :param id: MUST be a 4 digit hexadecimal string.
@@ -201,35 +206,55 @@ def _getTypedStream(self, filename, _type = None):
many cases cannot be predicted. As such, when using this function it is
best for you to check the type that it returns. If the function returns
None, that means it could not find the stream specified.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
"""
- self.__msg._getTypedStream(self, [self.__dir, filename], True, _type)
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Recipient instance has been garbage collected.')
+ return msg._getTypedStream(self, [self.__dir, filename], True, _type)
def exists(self, filename) -> bool:
"""
Checks if stream exists inside the recipient folder.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
"""
- return self.__msg.exists([self.__dir, filename])
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Recipient instance has been garbage collected.')
+ return msg.exists([self.__dir, filename])
def sExists(self, filename) -> bool:
"""
Checks if the string stream exists inside the recipient folder.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
"""
- return self.__msg.sExists([self.__dir, filename])
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Recipient instance has been garbage collected.')
+ return msg.sExists([self.__dir, filename])
def existsTypedProperty(self, id, _type = None) -> bool:
"""
Determines if the stream with the provided id exists. The return of this
function is 2 values, the first being a boolean for if anything was
found, and the second being how many were found.
+
+ :raises ReferenceError: The associated MSGFile instance has been garbage
+ collected.
"""
- return self.__msg.existsTypedProperty(id, self.__dir, _type, True, self.__props)
+ if (msg := self.__msg()) is None:
+ raise ReferenceError('The msg file for this Recipient instance has been garbage collected.')
+ return msg.existsTypedProperty(id, self.__dir, _type, True, self.__props)
- @property
+ @functools.cached_property
def account(self) -> Optional[str]:
"""
Returns the account of this recipient.
"""
- return self._ensureSet('_account', '__substg1.0_3A00')
+ return self._getStringStream('__substg1.0_3A00')
@property
def email(self) -> Optional[str]:
@@ -238,12 +263,12 @@ def email(self) -> Optional[str]:
"""
return self.__email
- @property
+ @functools.cached_property
def entryID(self) -> Optional[PermanentEntryID]:
"""
Returns the recipient's Entry ID.
"""
- return self._ensureSet('_entryID', '__substg1.0_0FFF0102', False, overrideClass = PermanentEntryID)
+ return self._getStreamAs('__substg1.0_0FFF0102', False, PermanentEntryID)
@property
def formatted(self) -> str:
@@ -252,12 +277,12 @@ def formatted(self) -> str:
"""
return self.__formatted
- @property
+ @functools.cached_property
def instanceKey(self) -> Optional[bytes]:
"""
Returns the instance key of this recipient.
"""
- return self._ensureSet('_instanceKey', '__substg1.0_0FF60102', False)
+ return self._getStream('__substg1.0_0FF60102')
@property
def name(self) -> Optional[str]:
@@ -267,39 +292,39 @@ def name(self) -> Optional[str]:
return self.__name
@property
- def props(self) -> Properties:
+ def props(self) -> PropertiesStore:
"""
Returns the Properties instance of the recipient.
"""
return self.__props
- @property
+ @functools.cached_property
def recordKey(self) -> Optional[bytes]:
"""
Returns the instance key of this recipient.
"""
- return self._ensureSet('_recordKey', '__substg1.0_0FF90102', False)
+ return self._getStream('__substg1.0_0FF90102')
- @property
+ @functools.cached_property
def searchKey(self) -> Optional[bytes]:
"""
Returns the search key of this recipient.
"""
- return self._ensureSet('_searchKey', '__substg1.0_300B0102', False)
+ return self._getStream('__substg1.0_300B0102')
- @property
+ @functools.cached_property
def smtpAddress(self) -> Optional[str]:
"""
Returns the SMTP address of this recipient.
"""
- return self._ensureSet('_smtpAddress', '__substg1.0_39FE')
+ return self._getStringStream('__substg1.0_39FE')
- @property
+ @functools.cached_property
def transmittableDisplayName(self) -> Optional[str]:
"""
Returns the transmittable display name of this recipient.
"""
- return self._ensureSet('_transmittableDisplayName', '__substg1.0_3A20')
+ return self._getStringStream('__substg1.0_3A20')
@property
def type(self) -> Union[RecipientType, MeetingRecipientType]:
diff --git a/extract_msg/structures/_helpers.py b/extract_msg/structures/_helpers.py
index a29d074f..7e3331cd 100644
--- a/extract_msg/structures/_helpers.py
+++ b/extract_msg/structures/_helpers.py
@@ -25,27 +25,27 @@ def __init__(self, *args, littleEndian = True, **kwargs):
super().__init__(*args, **kwargs)
self.__le = bool(littleEndian)
if self.__le:
- self.__int8_t = constants.ST_LE_I8
- self.__int16_t = constants.ST_LE_I16
- self.__int32_t = constants.ST_LE_I32
- self.__int64_t = constants.ST_LE_I64
- self.__uint8_t = constants.ST_LE_UI8
- self.__uint16_t = constants.ST_LE_UI16
- self.__uint32_t = constants.ST_LE_UI32
- self.__uint64_t = constants.ST_LE_UI64
- self.__float_t = constants.ST_LE_F32
- self.__double_t = constants.ST_LE_F64
+ self.__int8_t = constants.st.ST_LE_I8
+ self.__int16_t = constants.st.ST_LE_I16
+ self.__int32_t = constants.st.ST_LE_I32
+ self.__int64_t = constants.st.ST_LE_I64
+ self.__uint8_t = constants.st.ST_LE_UI8
+ self.__uint16_t = constants.st.ST_LE_UI16
+ self.__uint32_t = constants.st.ST_LE_UI32
+ self.__uint64_t = constants.st.ST_LE_UI64
+ self.__float_t = constants.st.ST_LE_F32
+ self.__double_t = constants.st.ST_LE_F64
else:
- self.__int8_t = constants.ST_BE_I8
- self.__int16_t = constants.ST_BE_I16
- self.__int32_t = constants.ST_BE_I32
- self.__int64_t = constants.ST_BE_I64
- self.__uint8_t = constants.ST_BE_UI8
- self.__uint16_t = constants.ST_BE_UI16
- self.__uint32_t = constants.ST_BE_UI32
- self.__uint64_t = constants.ST_BE_UI64
- self.__float_t = constants.ST_BE_F32
- self.__double_t = constants.ST_BE_F64
+ self.__int8_t = constants.st.ST_BE_I8
+ self.__int16_t = constants.st.ST_BE_I16
+ self.__int32_t = constants.st.ST_BE_I32
+ self.__int64_t = constants.st.ST_BE_I64
+ self.__uint8_t = constants.st.ST_BE_UI8
+ self.__uint16_t = constants.st.ST_BE_UI16
+ self.__uint32_t = constants.st.ST_BE_UI32
+ self.__uint64_t = constants.st.ST_BE_UI64
+ self.__float_t = constants.st.ST_BE_F32
+ self.__double_t = constants.st.ST_BE_F64
def _readDecodedString(self, encoding, width : int = 1) -> str:
"""
diff --git a/extract_msg/structures/business_card.py b/extract_msg/structures/business_card.py
index 92d3d2e0..24edad88 100644
--- a/extract_msg/structures/business_card.py
+++ b/extract_msg/structures/business_card.py
@@ -21,7 +21,7 @@ class BusinessCardDisplayDefinition:
def __init__(self, data : bytes):
self.__rawData = data
reader = BytesReader(data)
- unpacked = constants.ST_BC_HEAD.unpack(reader.read(13))
+ unpacked = constants.st.ST_BC_HEAD.unpack(reader.read(13))
# Because doc says it must be ignored, we don't check the reserved here.
reader.read(4)
self.__majorVersion = unpacked[0]
@@ -139,7 +139,7 @@ def templateID(self) -> BCTemplateID:
class FieldInfo:
def __init__(self, data : bytes, extraInfo : bytes):
self.__raw = data
- unpacked = constants.ST_BC_FIELD_INFO.unpack(data)
+ unpacked = constants.st.ST_BC_FIELD_INFO.unpack(data)
self.__textPropertyID = unpacked[0]
self.__textFormat = BCTextFormat(unpacked[1])
self.__labelFormat = BCLabelFormat(unpacked[2])
diff --git a/extract_msg/structures/entry_id.py b/extract_msg/structures/entry_id.py
index ab0a45c1..0ad03fe3 100644
--- a/extract_msg/structures/entry_id.py
+++ b/extract_msg/structures/entry_id.py
@@ -222,7 +222,7 @@ def __init__(self, data : bytes):
self.__folderType = MessageType(reader.readUnsignedShort())
self.__databaseGuid = bytesToGuid(reader.read(16))
# This entry is 6 bytes, so we pull some shenanigans to unpack it.
- self.__globalCounter = constants.ST_LE_UI64.unpack(reader.read(6) + b'\x00\x00')
+ self.__globalCounter = constants.st.ST_LE_UI64.unpack(reader.read(6) + b'\x00\x00')
reader.assertNull(2, 'Pad bytes were not 0.')
@property
@@ -262,11 +262,11 @@ def __init__(self, data : bytes):
self.__messageType = MessageType(reader.readUnsignedShort())
self.__folderDatabaseGuid = bytesToGuid(reader.read(16))
# This entry is 6 bytes, so we pull some shenanigans to unpack it.
- self.__folderGlobalCounter = constants.ST_LE_UI64.unpack(reader.read(6) + b'\x00\x00')
+ self.__folderGlobalCounter = constants.st.ST_LE_UI64.unpack(reader.read(6) + b'\x00\x00')
reader.assertNull(2, 'Pad bytes were not 0.')
self.__messageDatabaseGuid = bytesToGuid(reader.read(16))
# This entry is 6 bytes, so we pull some shenanigans to unpack it.
- self.__messageGlobalCounter = constants.ST_LE_UI64.unpack(reader.read(6) + b'\x00\x00')
+ self.__messageGlobalCounter = constants.st.ST_LE_UI64.unpack(reader.read(6) + b'\x00\x00')
reader.assertNull(2, 'Pad bytes were not 0.')
# Not sure why Microsoft decided to say "yes, let's do 2 6-byte integers
# followed by 2 pad bits each" instead of just 2 8-byte integers with a
@@ -453,7 +453,7 @@ class PermanentEntryID(EntryID):
def __init__(self, data : bytes):
super().__init__(data)
- unpacked = constants.STPEID.unpack(data[:28])
+ unpacked = constants.st.STPEID.unpack(data[:28])
if unpacked[0] != 0:
raise TypeError(f'Not a PermanentEntryID (expected 0, got {unpacked[0]}).')
self.__displayTypeString = DisplayType(unpacked[2])
diff --git a/extract_msg/structures/misc_id.py b/extract_msg/structures/misc_id.py
index 166987fd..20011945 100644
--- a/extract_msg/structures/misc_id.py
+++ b/extract_msg/structures/misc_id.py
@@ -28,9 +28,9 @@ class FolderID:
def __init__(self, data : bytes):
self.__rawData = data
- self.__replicaID = constants.STUI16.unpack(data[:2])
+ self.__replicaID = constants.st.STUI16.unpack(data[:2])
# This entry is 6 bytes, so we pull some shenanigans to unpack it.
- self.__globalCounter = constants.STUI64.unpack(data[2:8] + b'\x00\x00')
+ self.__globalCounter = constants.st.STUI64.unpack(data[2:8] + b'\x00\x00')
@property
def globalCounter(self) -> int:
@@ -68,7 +68,7 @@ def __init__(self, data : bytes):
self.__byteArrayID = reader.assertRead(expectedBytes, errorMsg)
self.__yh = reader.read(1)
self.__yl = reader.read(1)
- self.__year = constants.ST_BE_UI16.unpack(self.__yh + self.__yl)[0]
+ self.__year = constants.st.ST_BE_UI16.unpack(self.__yh + self.__yl)[0]
self.__month = reader.readUnsignedByte()
self.__day = reader.readUnsignedByte()
self.__creationTime = filetimeToDatetime(reader.readUnsignedLong())
@@ -141,9 +141,9 @@ class MessageID:
def __init__(self, data : bytes):
self.__rawData = data
- self.__replicaID = constants.STUI16.unpack(data[:2])
+ self.__replicaID = constants.st.STUI16.unpack(data[:2])
# This entry is 6 bytes, so we pull some shenanigans to unpack it.
- self.__globalCounter = constants.STUI64.unpack(data[2:8] + b'\x00\x00')
+ self.__globalCounter = constants.st.STUI64.unpack(data[2:8] + b'\x00\x00')
@property
def globalCounter(self) -> int:
@@ -192,7 +192,7 @@ def __init__(self, data : bytes):
self.__rawData = data
self.__folderID = FolderID(data[1:9])
self.__messageID = MessageID(data[9:17])
- self.__instance = constants.STUI32.unpack(data[17:21])
+ self.__instance = constants.st.STUI32.unpack(data[17:21])
@property
def folderID(self) -> FolderID:
diff --git a/extract_msg/structures/recurrence_pattern.py b/extract_msg/structures/recurrence_pattern.py
index c2cb2f56..5743f8d6 100644
--- a/extract_msg/structures/recurrence_pattern.py
+++ b/extract_msg/structures/recurrence_pattern.py
@@ -35,11 +35,11 @@ def __init__(self, data : bytes):
if self.__patternType == RecurPatternType.DAY:
self.__patternTypeSpecific = None
elif self.__patternType == RecurPatternType.WEEK:
- self.__patternTypeSpecific = RPTSW.fromBits(reader.readUnsignedInt())
+ self.__patternTypeSpecific = RPTSW(reader.readUnsignedInt())
elif self.__patternType in (RecurPatternType.MONTH_NTH, RecurPatternType.HJ_MONTH_NTH):
self.__patternTypeSpecific = reader.readUnsignedInt()
else:
- self.__patternTypeSpecific = (RPTSW.fromBits(reader.readUnsignedInt()),
+ self.__patternTypeSpecific = (RPTSW(reader.readUnsignedInt()),
RecurMonthNthWeek(reader.readUnsignedInt()))
self.__endType = RecurEndType.fromInt(reader.readUnsignedInt())
diff --git a/extract_msg/structures/system_time.py b/extract_msg/structures/system_time.py
index f5efc750..e9f91067 100644
--- a/extract_msg/structures/system_time.py
+++ b/extract_msg/structures/system_time.py
@@ -33,7 +33,7 @@ def pack(self) -> bytes:
"""
Packs the current data into bytes.
"""
- return constants.ST_SYSTEMTIME.pack(self.year, self.month,
+ return constants.st.ST_SYSTEMTIME.pack(self.year, self.month,
self.dayOfWeek, self.day, self.hour,
self.minute, self.second,
self.milliseconds)
@@ -42,7 +42,7 @@ def unpack(self, data : bytes) -> None:
"""
Fills out the fields of this instance by unpacking the bytes.
"""
- unpacked = constants.ST_SYSTEMTIME.unpack(data)
+ unpacked = constants.st.ST_SYSTEMTIME.unpack(data)
self.year = unpacked[0]
self.month = unpacked[1]
self.dayOfWeek = unpacked[2]
diff --git a/extract_msg/structures/time_zone_struct.py b/extract_msg/structures/time_zone_struct.py
index c0884e84..f3d97d1a 100644
--- a/extract_msg/structures/time_zone_struct.py
+++ b/extract_msg/structures/time_zone_struct.py
@@ -14,7 +14,7 @@ class TimeZoneStruct:
def __init__(self, data : bytes):
self.__rawData = data
- unpacked = constants.ST_TZ.unpack(data)
+ unpacked = constants.st.ST_TZ.unpack(data)
self.__bias = unpacked[0]
self.__standardBias = unpacked[1]
self.__daylightBias = unpacked[2]
diff --git a/extract_msg/structures/tz_rule.py b/extract_msg/structures/tz_rule.py
index e7017206..d5427ac8 100644
--- a/extract_msg/structures/tz_rule.py
+++ b/extract_msg/structures/tz_rule.py
@@ -24,7 +24,7 @@ def __init__(self, data : bytes):
self.__majorVersion = reader.readByte()
self.__minorVersion = reader.readByte()
reader.assertRead(b'\x3E\x00')
- self.__flags = TZFlag.fromBits(reader.readUnsignedShort())
+ self.__flags = TZFlag(reader.readUnsignedShort())
self.__year = reader.readShort()
# We *should* be doing this, but Outlook is violating the standard so...
#reader.assertNull(14)
diff --git a/extract_msg/utils.py b/extract_msg/utils.py
index 76b8c380..45e66e20 100644
--- a/extract_msg/utils.py
+++ b/extract_msg/utils.py
@@ -1,27 +1,55 @@
from __future__ import annotations
+
"""
Utility functions of extract_msg.
"""
__all__ = [
- 'addNumToDir', 'addNumToZipDir', 'bitwiseAdjust', 'bitwiseAdjustedAnd',
- 'bytesToGuid', 'ceilDiv', 'cloneOleFile', 'createZipOpen',
- 'dictGetCasedKey', 'divide', 'filetimeToDatetime', 'findWk',
- 'fromTimeStamp', 'getCommandArgs', 'getEncodingName', 'getFullClassName',
- 'hasLen', 'htmlSanitize', 'inputToBytes', 'inputToMsgPath', 'inputToString',
- 'isEncapsulatedRtf', 'isEmptyString', 'knownMsgClass', 'filetimeToUtc',
- 'msgPathToString', 'openMsg', 'openMsgBulk', 'parseType', 'prepareFilename',
- 'properHex', 'roundUp', 'rtfSanitizeHtml', 'rtfSanitizePlain',
- 'setupLogging', 'tryGetMimetype', 'unsignedToSignedInt', 'unwrapMsg',
- 'unwrapMultipart', 'validateHtml', 'verifyPropertyId', 'verifyType',
+ 'addNumToDir',
+ 'addNumToZipDir',
+ 'bitwiseAdjust',
+ 'bitwiseAdjustedAnd',
+ 'bytesToGuid',
+ 'ceilDiv',
+ 'cloneOleFile',
+ 'createZipOpen',
+ 'dictGetCasedKey',
+ 'divide',
+ 'filetimeToDatetime',
+ 'filetimeToUtc',
+ 'findWk',
+ 'fromTimeStamp',
+ 'getCommandArgs',
+ 'hasLen',
+ 'htmlSanitize',
+ 'inputToBytes',
+ 'inputToMsgPath',
+ 'inputToString',
+ 'isEncapsulatedRtf',
+ 'isEmptyString',
+ 'makeWeakRef',
+ 'msgPathToString',
+ 'parseType',
+ 'prepareFilename',
+ 'properHex',
+ 'roundUp',
+ 'rtfSanitizeHtml',
+ 'rtfSanitizePlain',
+ 'setupLogging',
+ 'tryGetMimetype',
+ 'unsignedToSignedInt',
+ 'unwrapMsg',
+ 'unwrapMultipart',
+ 'validateHtml',
+ 'verifyPropertyId',
+ 'verifyType',
'windowsUnicode',
]
import argparse
-import codecs
import collections
import copy
import datetime
@@ -36,8 +64,7 @@
import pathlib
import shutil
import struct
-# Not actually sure if this needs to be here for the logging, so just in case.
-import sys
+import weakref
import zipfile
import bs4
@@ -45,26 +72,26 @@
import tzlocal
from html import escape as htmlEscape
-from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING, Union
+from typing import Any, Dict, List, Optional, TypeVar, TYPE_CHECKING, Union
from . import constants
from .enums import AttachmentType
from .exceptions import (
ConversionError, ExecutableNotFound, IncompatibleOptionsError,
- InvalidFileFormatError, InvaildPropertyIdError, TZError,
- UnknownCodepageError, UnknownTypeError, UnrecognizedMSGTypeError,
- UnsupportedEncodingError, UnsupportedMSGTypeError
+ InvaildPropertyIdError, TZError, UnknownTypeError
)
# Allow for nice type checking.
if TYPE_CHECKING:
- from .msg import MSGFile
+ from .msg_classes.msg import MSGFile
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
logging.addLevelName(5, 'DEVELOPER')
+_T = TypeVar("_T")
+
def addNumToDir(dirName : pathlib.Path) -> Optional[pathlib.Path]:
"""
@@ -127,7 +154,7 @@ def bytesToGuid(bytesInput : bytes) -> str:
"""
Converts a bytes instance to a GUID.
"""
- guidVals = constants.ST_GUID.unpack(bytesInput)
+ guidVals = constants.st.ST_GUID.unpack(bytesInput)
return f'{{{guidVals[0]:08X}-{guidVals[1]:04X}-{guidVals[2]:04X}-{guidVals[3][:2].hex().upper()}-{guidVals[3][2:].hex().upper()}}}'
@@ -379,6 +406,9 @@ def getCommandArgs(args) -> argparse.Namespace:
# --extract-embedded
parser.add_argument('--extract-embedded', dest='extractEmbedded', action='store_true',
help='Extracts the embedded MSG files as MSG files instead of running their save functions.')
+ # --overwrite-existing
+ parser.add_argument('--overwrite-existing', dest='overwriteExisting', action='store_true',
+ help='Disables filename conflict resolution code for attachments when saving a file, causing files to be overwriten if two attachments with the same filename are on an MSG file.')
# --skip-not-implemented
parser.add_argument('--skip-not-implemented', '--skip-ni', dest='skipNotImplemented', action='store_true',
help='Skips any attachments that are not implemented, allowing saving of the rest of the message.')
@@ -469,27 +499,6 @@ def getCommandArgs(args) -> argparse.Namespace:
return options
-
-def getEncodingName(codepage : int) -> str:
- """
- Returns the name of the encoding with the specified codepage.
-
- :raises UnknownCodepageError: if the codepage is unrecognized.
- :raises UnsupportedEncodingError: if the codepage is not supported.
- """
- if codepage not in constants.CODE_PAGES:
- raise UnknownCodepageError(str(codepage))
- try:
- codecs.lookup(constants.CODE_PAGES[codepage])
- return constants.CODE_PAGES[codepage]
- except LookupError:
- raise UnsupportedEncodingError(f'The codepage {codepage} ({constants.CODE_PAGES[codepage]}) is not currently supported by your version of Python.')
-
-
-def getFullClassName(inp) -> str:
- return inp.__class__.__module__ + '.' + inp.__class__.__name__
-
-
def hasLen(obj) -> bool:
"""
Checks if :param obj: has a __len__ attribute.
@@ -509,7 +518,7 @@ def htmlSanitize(inp : str) -> str:
inp = inp.replace('\r\n', '\n').replace('\n', '
')
# Escape long sections of spaces to ensure they won't be ignored.
- inp = constants.RE_HTML_SAN_SPACE.sub((lambda spaces : ' ' * len(spaces.group(0))),inp)
+ inp = constants.re.HTML_SAN_SPACE.sub((lambda spaces : ' ' * len(spaces.group(0))),inp)
return inp
@@ -588,27 +597,22 @@ def isEmptyString(inp : str) -> bool:
return (inp == '' or inp is None)
-def knownMsgClass(classType : str) -> bool:
+def filetimeToUtc(inp : int) -> float:
"""
- Checks if the specified class type is recognized by the module. Usually used
- for checking if a type is simply unsupported rather than unknown.
+ Converts a FILETIME into a unix timestamp.
"""
- classType = classType.lower()
- if classType == 'ipm':
- return True
-
- for item in constants.KNOWN_CLASS_TYPES:
- if classType.startswith(item):
- return True
-
- return False
+ return (inp - 116444736000000000) / 10000000.0
-def filetimeToUtc(inp : int) -> float:
+def makeWeakRef(obj : Optional[_T]) -> Optional[weakref.ReferenceType[_T]]:
"""
- Converts a FILETIME into a unix timestamp.
+ Attempts to return a weak reference to the object, returning None if not
+ possible.
"""
- return (inp - 116444736000000000) / 10000000.0
+ try:
+ return weakref.ref(obj)
+ except TypeError:
+ return None
def msgPathToString(inp) -> str:
@@ -624,165 +628,6 @@ def msgPathToString(inp) -> str:
return inp
-def openMsg(path, **kwargs) -> MSGFile:
- """
- Function to automatically open an MSG file and detect what type it is.
-
- :param path: Path to the msg file in the system or is the raw msg file.
- :param prefix: Used for extracting embeded msg files inside the main one.
- Do not set manually unless you know what you are doing.
- :param parentMsg: Used for syncronizing named properties instances. Do not
- set this unless you know what you are doing.
- :param attachmentClass: Optional, the class the Message object will use for
- attachments. You probably should not change this value unless you know
- what you are doing.
- :param signedAttachmentClass: Optional, the class the object will use for
- signed attachments.
- :param filename: Optional, the filename to be used by default when saving.
- :param delayAttachments: Optional, delays the initialization of attachments
- until the user attempts to retrieve them. Allows MSG files with bad
- attachments to be initialized so the other data can be retrieved.
- :param overrideEncoding: Optional, overrides the specified encoding of the
- MSG file.
- :param attachmentErrorBehavior: Optional, the behaviour to use in the event
- of an error when parsing the attachments.
- :param recipientSeparator: Optional, Separator string to use between
- recipients.
- :param ignoreRtfDeErrors: Optional, specifies that any errors that occur
- from the usage of RTFDE should be ignored (default: False).
-
- If :param strict: is set to `True`, this function will raise an exception
- when it cannot identify what MSGFile derivitive to use. Otherwise, it will
- log the error and return a basic MSGFile instance.
-
- :raises UnsupportedMSGTypeError: if the type is recognized but not suppoted.
- :raises UnrecognizedMSGTypeError: if the type is not recognized.
- """
- from .appointment import AppointmentMeeting
- from .contact import Contact
- from .meeting_cancellation import MeetingCancellation
- from .meeting_exception import MeetingException
- from .meeting_forward import MeetingForwardNotification
- from .meeting_request import MeetingRequest
- from .meeting_response import MeetingResponse
- from .message import Message
- from .msg import MSGFile
- from .message_signed import MessageSigned
- from .post import Post
- from .task import Task
- from .task_request import TaskRequest
-
- # When the initial MSG file is opened, it should *always* delay attachments
- # so it can get the main class type. We only need to load them after that
- # if we are directly returning the MSGFile instance *and* delayAttachments
- # is False.
- #
- # So first let's store the original value.
- delayAttachments = kwargs.get('delayAttachments', False)
- kwargs['delayAttachments'] = True
-
- msg = MSGFile(path, **kwargs)
-
- # Restore the option in the kwargs so we don't have to worry about it.
- kwargs['delayAttachments'] = delayAttachments
-
- # After rechecking the docs, all comparisons should be case-insensitive, not
- # case-sensitive. My reading ability is great.
- #
- # Also after consideration, I realized we need to be very careful here, as
- # other file types (like doc, ppt, etc.) might open but not return a class
- # type. If the stream is not found, classType returns None, which has no
- # lower function. So let's make sure we got a good return first.
- if not msg.classType:
- if kwargs.get('strict', True):
- raise InvalidFileFormatError('File was confirmed to be an olefile, but was not an MSG file.')
- else:
- # If strict mode is off, we'll just return an MSGFile anyways.
- logging.critical('Received file that was an olefile but was not an MSG file. Returning MSGFile anyways because strict mode is off.')
- return msg
- classType = msg.classType.lower()
- # Put the message class first as it is most common.
- if classType.startswith('ipm.note') or classType.startswith('report'):
- msg.close()
- if classType.endswith('smime.multipartsigned') or classType.endswith('smime'):
- return MessageSigned(path, **kwargs)
- else:
- return Message(path, **kwargs)
- elif classType.startswith('ipm.appointment'):
- msg.close()
- return AppointmentMeeting(path, **kwargs)
- elif classType.startswith('ipm.contact') or classType.startswith('ipm.distlist'):
- msg.close()
- return Contact(path, **kwargs)
- elif classType.startswith('ipm.post'):
- msg.close()
- return Post(path, **kwargs)
- elif classType.startswith('ipm.schedule.meeting.request'):
- msg.close()
- return MeetingRequest(path, **kwargs)
- elif classType.startswith('ipm.schedule.meeting.canceled'):
- msg.close()
- return MeetingCancellation(path, **kwargs)
- elif classType.startswith('ipm.schedule.meeting.notification.forward'):
- msg.close()
- return MeetingForwardNotification(path, **kwargs)
- elif classType.startswith('ipm.schedule.meeting.resp'):
- msg.close()
- return MeetingResponse(path, **kwargs)
- elif classType.startswith('ipm.taskrequest'):
- msg.close()
- return TaskRequest(path, **kwargs)
- elif classType.startswith('ipm.task'):
- msg.close()
- return Task(path, **kwargs)
- elif classType.startswith('ipm.ole.class.{00061055-0000-0000-c000-000000000046}'):
- # Exception objects have a weird class type.
- msg.close()
- return MeetingException(path, **kwargs)
- elif classType == 'ipm':
- # Unspecified format. It should be equal to this and not just start with
- # it.
- if not delayAttachments:
- msg.attachments
- return msg
- elif kwargs.get('strict', True):
- # Because we are closing it, we need to store it in a variable first.
- ct = msg.classType
- msg.close()
- if knownMsgClass(classType):
- raise UnsupportedMSGTypeError(f'MSG type "{ct}" currently is not supported by the module. If you would like support, please make a feature request.')
- raise UnrecognizedMSGTypeError(f'Could not recognize msg class type "{ct}".')
- else:
- logger.error(f'Could not recognize msg class type "{msg.classType}". This most likely means it hasn\'t been implemented yet, and you should ask the developers to add support for it.')
- if not delayAttachments:
- msg.attachments
- return msg
-
-
-def openMsgBulk(path, **kwargs) -> Union[List[MSGFile], Tuple[Exception, Union[str, bytes]]]:
- """
- Takes the same arguments as openMsg, but opens a collection of msg files
- based on a wild card. Returns a list if successful, otherwise returns a
- tuple.
-
- :param ignoreFailures: If this is True, will return a list of all successful
- files, ignoring any failures. Otherwise, will close all that
- successfully opened, and return a tuple of the exception and the path of
- the file that failed.
- """
- files = []
- for x in glob.glob(str(path)):
- try:
- files.append(openMsg(x, **kwargs))
- except Exception as e:
- if not kwargs.get('ignoreFailures', False):
- for msg in files:
- msg.close()
- return (e, x)
-
- return files
-
-
def parseType(_type : int, stream, encoding, extras):
"""
Converts the data in :param stream: to a much more accurate type, specified
@@ -808,21 +653,21 @@ def parseType(_type : int, stream, encoding, extras):
logger.warning('Property type is PtypNull, but is not equal to 0.')
return None
elif _type == 0x0002: # PtypInteger16
- return constants.STI16.unpack(value)[0]
+ return constants.st.STI16.unpack(value)[0]
elif _type == 0x0003: # PtypInteger32
- return constants.STI32.unpack(value)[0]
+ return constants.st.STI32.unpack(value)[0]
elif _type == 0x0004: # PtypFloating32
- return constants.STF32.unpack(value)[0]
+ return constants.st.STF32.unpack(value)[0]
elif _type == 0x0005: # PtypFloating64
- return constants.STF64.unpack(value)[0]
+ return constants.st.STF64.unpack(value)[0]
elif _type == 0x0006: # PtypCurrency
- return (constants.STI64.unpack(value)[0]) / 10000.0
+ return (constants.st.STI64.unpack(value)[0]) / 10000.0
elif _type == 0x0007: # PtypFloatingTime
- value = constants.STF64.unpack(value)[0]
+ value = constants.st.STF64.unpack(value)[0]
return constants.PYTPFLOATINGTIME_START + datetime.timedelta(days = value)
elif _type == 0x000A: # PtypErrorCode
from .enums import ErrorCode, ErrorCodeType
- value = constants.STUI32.unpack(value)[0]
+ value = constants.st.STUI32.unpack(value)[0]
try:
value = ErrorCodeType(value)
except ValueError:
@@ -836,7 +681,7 @@ def parseType(_type : int, stream, encoding, extras):
pass
return value
elif _type == 0x000B: # PtypBoolean
- return constants.ST3.unpack(value)[0] == 1
+ return constants.st.ST3.unpack(value)[0] == 1
elif _type == 0x000D: # PtypObject/PtypEmbeddedTable
# TODO parsing for this.
# Wait, that's the extension for an attachment folder, so parsing this
@@ -844,18 +689,18 @@ def parseType(_type : int, stream, encoding, extras):
# without support for this.
raise NotImplementedError('Current version of extract-msg does not support the parsing of PtypObject/PtypEmbeddedTable in this function.')
elif _type == 0x0014: # PtypInteger64
- return constants.STI64.unpack(value)[0]
+ return constants.st.STI64.unpack(value)[0]
elif _type == 0x001E: # PtypString8
return value.decode(encoding)
elif _type == 0x001F: # PtypString
return value.decode('utf-16-le')
elif _type == 0x0040: # PtypTime
- rawTime = constants.ST3.unpack(value)[0]
+ rawTime = constants.st.ST3.unpack(value)[0]
return filetimeToDatetime(rawTime)
elif _type == 0x0048: # PtypGuid
return bytesToGuid(value)
elif _type == 0x00FB: # PtypServerId
- count = constants.STUI16.unpack(value[:2])
+ count = constants.st.STUI16.unpack(value[:2])
# If the first byte is a 1 then it uses the ServerID structure.
if value[3] == 1:
from .structures.misc_id import ServerID
@@ -884,7 +729,7 @@ def parseType(_type : int, stream, encoding, extras):
return ret
elif _type == 0x1102: # PtypMultipleBinary
ret = copy.deepcopy(extras)
- lengths = tuple(constants.STUI32.unpack(stream[pos*8:(pos+1)*8])[0] for pos in range(len(stream) // 8))
+ lengths = tuple(constants.st.STUI32.unpack(stream[pos*8:(pos+1)*8])[0] for pos in range(len(stream) // 8))
lengthLengths = len(lengths)
if lengthLengths > lengthExtras:
logger.warning(f'Error while parsing multiple type. Expected {lengthLengths} stream{"s" if lengthLengths != 1 else ""}, got {lengthExtras}. Ignoring.')
@@ -896,20 +741,20 @@ def parseType(_type : int, stream, encoding, extras):
if stream != len(extras):
logger.warning(f'Error while parsing multiple type. Expected {stream} entr{"y" if stream == 1 else "ies"}, got {len(extras)}. Ignoring.')
if _type == 0x1002: # PtypMultipleInteger16
- return tuple(constants.STMI16.unpack(x)[0] for x in extras)
+ return tuple(constants.st.STMI16.unpack(x)[0] for x in extras)
if _type == 0x1003: # PtypMultipleInteger32
- return tuple(constants.STMI32.unpack(x)[0] for x in extras)
+ return tuple(constants.st.STMI32.unpack(x)[0] for x in extras)
if _type == 0x1004: # PtypMultipleFloating32
- return tuple(constants.STMF32.unpack(x)[0] for x in extras)
+ return tuple(constants.st.STMF32.unpack(x)[0] for x in extras)
if _type == 0x1005: # PtypMultipleFloating64
- return tuple(constants.STMF64.unpack(x)[0] for x in extras)
+ return tuple(constants.st.STMF64.unpack(x)[0] for x in extras)
if _type == 0x1007: # PtypMultipleFloatingTime
- values = tuple(constants.STMF64.unpack(x)[0] for x in extras)
+ values = tuple(constants.st.STMF64.unpack(x)[0] for x in extras)
return tuple(constants.PYTPFLOATINGTIME_START + datetime.timedelta(days = amount) for amount in values)
if _type == 0x1014: # PtypMultipleInteger64
- return tuple(constants.STMI64.unpack(x)[0] for x in extras)
+ return tuple(constants.st.STMI64.unpack(x)[0] for x in extras)
if _type == 0x1040: # PtypMultipleTime
- return tuple(filetimeToUtc(constants.ST3.unpack(x)[0]) for x in extras)
+ return tuple(filetimeToUtc(constants.st.ST3.unpack(x)[0]) for x in extras)
if _type == 0x1048: # PtypMultipleGuid
return tuple(bytesToGuid(x) for x in extras)
else:
@@ -1022,7 +867,7 @@ def setupLogging(defaultPath = None, defaultLevel = logging.WARN, logfile = None
Returns:
bool: True if the configuration file was found and applied, False otherwise
"""
- shippedConfig = pathlib.Path(__file__).parent / 'logging-config'
+ shippedConfig = pathlib.Path(__file__).parent / 'data' / 'logging-config'
if os.name == 'nt':
null = 'NUL'
shippedConfig /= 'logging-nt.json'
@@ -1127,7 +972,7 @@ def unsignedToSignedInt(uInt : int) -> int:
raise ValueError('Value is too large.')
if uInt < 0:
raise ValueError('Value is already signed.')
- return constants.STI32.unpack(constants.STUI32.pack(uInt))[0]
+ return constants.st.STI32.unpack(constants.st.STUI32.pack(uInt))[0]
def unwrapMsg(msg : MSGFile) -> Dict:
@@ -1139,7 +984,7 @@ def unwrapMsg(msg : MSGFile) -> Dict:
(including the original in the first index), and "raw_attachments" for raw
attachments from signed messages.
"""
- from .message_signed_base import MessageSignedBase
+ from .msg_classes import MessageSignedBase
# Here is where we store main attachments.
attachments = []
@@ -1167,9 +1012,9 @@ def unwrapMsg(msg : MSGFile) -> Dict:
for att in currentItem.attachments:
# If it is a regular attachment, add it to the list. Otherwise, add
# it to be processed
- if att.type in (AttachmentType.DATA, AttachmentType.SIGNED):
+ if att.type not in (AttachmentType.MSG, AttachmentType.SIGNED_EMBEDDED):
attachments.append(att)
- elif att.type is AttachmentType.MSG:
+ else:
# Here we do two things. The first is we store it to the output
# so we can return it. The second is we add it to the processing
# list. The reason this is two steps is because we need to be
@@ -1315,6 +1160,7 @@ def unwrapMultipart(mp : Union[bytes, str, email.message.Message]) -> Dict:
'html_body': htmlBody,
}
+
def validateHtml(html : bytes) -> bool:
"""
Checks whether the HTML is considered valid. To be valid, the HTML must, at
@@ -1359,3 +1205,4 @@ def verifyType(_type) -> str:
def windowsUnicode(string) -> Optional[str]:
return str(string, 'utf-16-le') if string is not None else None
+
diff --git a/helper-scripts/detect-prop-overlap.py b/helper-scripts/detect-prop-overlap.py
index f3209b51..14e94a47 100644
--- a/helper-scripts/detect-prop-overlap.py
+++ b/helper-scripts/detect-prop-overlap.py
@@ -10,26 +10,27 @@ def main(args):
overlapping if they share the same variable name or come from the same
property. This may be intentional for some properties.
"""
+ raise Exception('This script needs to be rewritten for the new property system.')
if len(args) < 2:
print('Please specify a file to read.')
sys.exit(1)
- pattern = re.compile(r"(?<=self._ensureSet)((Named)|(Property)|(Typed))?\('(.*?)', '(.*?)'")
+ pattern = re.compile(r"(?<=self._get)((Named)|(Property)|(Typed)|(Stream))As\('(.*?)'")
for patt in args[1:]:
for name in glob.glob(patt):
with open(name, 'r', encoding = 'utf-8') as f:
data = f.read()
- names = tuple(sorted(x.group(5) for x in pattern.finditer(data)))
+ #names = tuple(sorted(x.group(5) for x in pattern.finditer(data)))
ids = tuple(sorted(x.group(6) for x in pattern.finditer(data)))
- duplicateNamesFound = len(names) != len(list(set(names)))
+ #duplicateNamesFound = len(names) != len(list(set(names)))
duplicateIdsFound = len(ids) != len(list(set(names)))
print(name)
- if duplicateNamesFound:
+ if False:#duplicateNamesFound:
print('\tVariable Names:')
counts = {x: 0 for x in names}
for x in names:
@@ -47,7 +48,7 @@ def main(args):
if counts[x] > 1:
print(f'\t\t{x}')
- if not duplicateIdsFound and not duplicateNamesFound:
+ if not duplicateIdsFound:# and not duplicateNamesFound:
print('\tNo duplicates detected.')
print()
diff --git a/helper-scripts/produce-dec-table.py b/helper-scripts/produce-dec-table.py
new file mode 100644
index 00000000..3e699f49
--- /dev/null
+++ b/helper-scripts/produce-dec-table.py
@@ -0,0 +1,39 @@
+"""
+Basic script to convert a document for a single-byte encoding into a decoding
+table for use by the module.
+"""
+
+
+import sys
+
+
+conversionDict = {0:"'\\x00'",1:"'\\x01'",2:"'\\x02'",3:"'\\x03'",4:"'\\x04'",5:"'\\x05'",6:"'\\x06'",7:"'\\x07'",8:"'\\x08'",9:"'\\t'",10:"'\\n'",11:"'\\x0b'",12:"'\\x0c'",13:"'\\r'",14:"'\\x0e'",15:"'\\x0f'",16:"'\\x10'",17:"'\\x11'",18:"'\\x12'",19:"'\\x13'",20:"'\\x14'",21:"'\\x15'",22:"'\\x16'",23:"'\\x17'",24:"'\\x18'",25:"'\\x19'",26:"'\\x1a'",27:"'\\x1b'",28:"'\\x1c'",29:"'\\x1d'",30:"'\\x1e'",31:"'\\x1f'",32:"' '",33:"'!'",34:'\'"\'',35:"'#'",36:"'$'",37:"'%'",38:"'&'",39:'"\'"',40:"'('",41:"')'",42:"'*'",43:"'+'",44:"','",45:"'-'",46:"'.'",47:"'/'",48:"'0'",49:"'1'",50:"'2'",51:"'3'",52:"'4'",53:"'5'",54:"'6'",55:"'7'",56:"'8'",57:"'9'",58:"':'",59:"';'",60:"'<'",61:"'='",62:"'>'",63:"'?'",64:"'@'",65:"'A'",66:"'B'",67:"'C'",68:"'D'",69:"'E'",70:"'F'",71:"'G'",72:"'H'",73:"'I'",74:"'J'",75:"'K'",76:"'L'",77:"'M'",78:"'N'",79:"'O'",80:"'P'",81:"'Q'",82:"'R'",83:"'S'",84:"'T'",85:"'U'",86:"'V'",87:"'W'",88:"'X'",89:"'Y'",90:"'Z'",91:"'['",92:"'\\\\'",93:"']'",94:"'^'",95:"'_'",96:"'`'",97:"'a'",98:"'b'",99:"'c'",100:"'d'",101:"'e'",102:"'f'",103:"'g'",104:"'h'",105:"'i'",106:"'j'",107:"'k'",108:"'l'",109:"'m'",110:"'n'",111:"'o'",112:"'p'",113:"'q'",114:"'r'",115:"'s'",116:"'t'",117:"'u'",118:"'v'",119:"'w'",120:"'x'",121:"'y'",122:"'z'",123:"'{'",124:"'|'",125:"'}'",126:"'~'",127:"'\\x7f'",128:"'\\x80'",129:"'\\x81'",130:"'\\x82'",131:"'\\x83'",132:"'\\x84'",133:"'\\x85'",134:"'\\x86'",135:"'\\x87'",136:"'\\x88'",137:"'\\x89'",138:"'\\x8A'",139:"'\\x8B'",140:"'\\x8C'",141:"'\\x8D'",142:"'\\x8E'",143:"'\\x8F'",144:"'\\x90'",145:"'\\x91'",146:"'\\x92'",147:"'\\x93'",148:"'\\x94'",149:"'\\x95'",150:"'\\x96'",151:"'\\x97'",152:"'\\x98'",153:"'\\x99'",154:"'\\x9A'",155:"'\\x9B'",156:"'\\x9C'",157:"'\\x9D'",158:"'\\x9E'",159:"'\\x9F'",160:"'\\xA0'",161:"'\\xA1'",162:"'\\xA2'",163:"'\\xA3'",164:"'\\xA4'",165:"'\\xA5'",166:"'\\xA6'",167:"'\\xA7'",168:"'\\xA8'",169:"'\\xA9'",170:"'\\xAA'",171:"'\\xAB'",172:"'\\xAC'",173:"'\\xAD'",174:"'\\xAE'",175:"'\\xAF'",176:"'\\xB0'",177:"'\\xB1'",178:"'\\xB2'",179:"'\\xB3'",180:"'\\xB4'",181:"'\\xB5'",182:"'\\xB6'",183:"'\\xB7'",184:"'\\xB8'",185:"'\\xB9'",186:"'\\xBA'",187:"'\\xBB'",188:"'\\xBC'",189:"'\\xBD'",190:"'\\xBE'",191:"'\\xBF'",192:"'\\xC0'",193:"'\\xC1'",194:"'\\xC2'",195:"'\\xC3'",196:"'\\xC4'",197:"'\\xC5'",198:"'\\xC6'",199:"'\\xC7'",200:"'\\xC8'",201:"'\\xC9'",202:"'\\xCA'",203:"'\\xCB'",204:"'\\xCC'",205:"'\\xCD'",206:"'\\xCE'",207:"'\\xCF'",208:"'\\xD0'",209:"'\\xD1'",210:"'\\xD2'",211:"'\\xD3'",212:"'\\xD4'",213:"'\\xD5'",214:"'\\xD6'",215:"'\\xD7'",216:"'\\xD8'",217:"'\\xD9'",218:"'\\xDA'",219:"'\\xDB'",220:"'\\xDC'",221:"'\\xDD'",222:"'\\xDE'",223:"'\\xDF'",224:"'\\xE0'",225:"'\\xE1'",226:"'\\xE2'",227:"'\\xE3'",228:"'\\xE4'",229:"'\\xE5'",230:"'\\xE6'",231:"'\\xE7'",232:"'\\xE8'",233:"'\\xE9'",234:"'\\xEA'",235:"'\\xEB'",236:"'\\xEC'",237:"'\\xED'",238:"'\\xEE'",239:"'\\xEF'",240:"'\\xF0'",241:"'\\xF1'",242:"'\\xF2'",243:"'\\xF3'",244:"'\\xF4'",245:"'\\xF5'",246:"'\\xF6'",247:"'\\xF7'",248:"'\\xF8'",249:"'\\xF9'",250:"'\\xFA'",251:"'\\xFB'",252:"'\\xFC'",253:"'\\xFD'",254:"'\\xFE'",255:"'\\xFF'"}
+
+def symbolToEntry(input : int, output : int) -> str:
+ if output < 256:
+ return f'{input}:{conversionDict[output]}'
+ elif output < 0x10000:
+ return f'{input}:\'\\u{output:04X}\''
+ else:
+ return f'{input}:\'\\U{output:08X}\''
+
+
+if __name__ == "__main__":
+ table = {}
+ if len(sys.argv) != 2:
+ print('Invalid number of arguments (requires exactly one file name)!')
+ exit(-1)
+
+ with open(sys.argv[1], 'r') as f:
+ for line in f:
+ # Skip comment lines.
+ if not line.strip().startswith('#'):
+ items = line.split('\t')
+ if len(items) != 1 and items[1]:
+ table[int(items[0], 16)] = int(items[1], 16)
+
+ with open('.'.join(sys.argv[1].split('.')[:-1] + ['py']), 'w') as f:
+ # Write the top of the file.
+ f.write('__all__ = [\n \'decodingTable\',\n]\n\n\ndecodingTable={')
+ f.write(','.join(symbolToEntry(x, table[x]) for x in table))
+ f.write('}')
\ No newline at end of file
diff --git a/pyrightconfig.json b/pyrightconfig.json
new file mode 100644
index 00000000..31845b85
--- /dev/null
+++ b/pyrightconfig.json
@@ -0,0 +1,4 @@
+{
+ "pythonVersion": "3.8",
+ "pythonPlatform": "All"
+}
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 3125d5d8..def1fce6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,6 +6,5 @@ tzlocal>=4.2,<6
compressed_rtf>=1.0.6,<2
ebcdic>=1.1.1,<2
beautifulsoup4>=4.11.1,<4.13
-RTFDE==0.0.2
-chardet>=4.0.0,<6
+RTFDE>=0.1.0,<0.2
red-black-tree-mod==1.20
diff --git a/setup.cfg b/setup.cfg
index 951dc042..dc7d7bb5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -4,5 +4,8 @@ universal=1
[options.extras_require]
all =
extract-msg[mime]
+ extract-msg[image]
mime =
python-magic>=0.4.27,<0.5
+image =
+ Pillow>=9.5.0,<10
\ No newline at end of file
diff --git a/tests.py b/tests.py
index a2ea3872..b133ec71 100644
--- a/tests.py
+++ b/tests.py
@@ -9,4 +9,5 @@
from extract_msg_tests import *
-unittest.main(verbosity = 2)
+if __name__ == '__main__':
+ unittest.main(verbosity = 2)