diff --git a/0.7.0/.buildinfo b/0.7.0/.buildinfo new file mode 100644 index 0000000..521911e --- /dev/null +++ b/0.7.0/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 8c521714deba6476260c858b907aee33 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/0.7.0/.doctrees/changes.doctree b/0.7.0/.doctrees/changes.doctree new file mode 100644 index 0000000..26690ec Binary files /dev/null and b/0.7.0/.doctrees/changes.doctree differ diff --git a/0.7.0/.doctrees/contact.doctree b/0.7.0/.doctrees/contact.doctree new file mode 100644 index 0000000..31c4c2f Binary files /dev/null and b/0.7.0/.doctrees/contact.doctree differ diff --git a/0.7.0/.doctrees/dev/concepts.doctree b/0.7.0/.doctrees/dev/concepts.doctree new file mode 100644 index 0000000..3e580f5 Binary files /dev/null and b/0.7.0/.doctrees/dev/concepts.doctree differ diff --git a/0.7.0/.doctrees/dev/concepts/all_in_one_debugging.doctree b/0.7.0/.doctrees/dev/concepts/all_in_one_debugging.doctree new file mode 100644 index 0000000..1417c52 Binary files /dev/null and b/0.7.0/.doctrees/dev/concepts/all_in_one_debugging.doctree differ diff --git a/0.7.0/.doctrees/dev/concepts/anatomy_of_a_plugin.doctree b/0.7.0/.doctrees/dev/concepts/anatomy_of_a_plugin.doctree new file mode 100644 index 0000000..bd4e2bd Binary files /dev/null and b/0.7.0/.doctrees/dev/concepts/anatomy_of_a_plugin.doctree differ diff --git a/0.7.0/.doctrees/dev/concepts/data_transformations.doctree b/0.7.0/.doctrees/dev/concepts/data_transformations.doctree new file mode 100644 index 0000000..13267d1 Binary files /dev/null and b/0.7.0/.doctrees/dev/concepts/data_transformations.doctree differ diff --git a/0.7.0/.doctrees/dev/concepts/extraction_plugins.doctree b/0.7.0/.doctrees/dev/concepts/extraction_plugins.doctree new file mode 100644 index 0000000..d637e74 Binary files /dev/null and b/0.7.0/.doctrees/dev/concepts/extraction_plugins.doctree differ diff --git a/0.7.0/.doctrees/dev/concepts/hql_lite.doctree b/0.7.0/.doctrees/dev/concepts/hql_lite.doctree new file mode 100644 index 0000000..504c911 Binary files /dev/null and b/0.7.0/.doctrees/dev/concepts/hql_lite.doctree differ diff --git a/0.7.0/.doctrees/dev/concepts/isolation.doctree b/0.7.0/.doctrees/dev/concepts/isolation.doctree new file mode 100644 index 0000000..cfd8149 Binary files /dev/null and b/0.7.0/.doctrees/dev/concepts/isolation.doctree differ diff --git a/0.7.0/.doctrees/dev/concepts/kubernetes_autoscaling.doctree b/0.7.0/.doctrees/dev/concepts/kubernetes_autoscaling.doctree new file mode 100644 index 0000000..69d3207 Binary files /dev/null and b/0.7.0/.doctrees/dev/concepts/kubernetes_autoscaling.doctree differ diff --git a/0.7.0/.doctrees/dev/concepts/plugin_naming_convention.doctree b/0.7.0/.doctrees/dev/concepts/plugin_naming_convention.doctree new file mode 100644 index 0000000..c7de0c4 Binary files /dev/null and b/0.7.0/.doctrees/dev/concepts/plugin_naming_convention.doctree differ diff --git a/0.7.0/.doctrees/dev/concepts/plugin_types.doctree b/0.7.0/.doctrees/dev/concepts/plugin_types.doctree new file mode 100644 index 0000000..ee0cf8c Binary files /dev/null and b/0.7.0/.doctrees/dev/concepts/plugin_types.doctree differ diff --git a/0.7.0/.doctrees/dev/concepts/test_framework.doctree b/0.7.0/.doctrees/dev/concepts/test_framework.doctree new file mode 100644 index 0000000..3ae0356 Binary files /dev/null and b/0.7.0/.doctrees/dev/concepts/test_framework.doctree differ diff --git a/0.7.0/.doctrees/dev/concepts/traces.doctree b/0.7.0/.doctrees/dev/concepts/traces.doctree new file mode 100644 index 0000000..9f3c039 Binary files /dev/null and b/0.7.0/.doctrees/dev/concepts/traces.doctree differ diff --git a/0.7.0/.doctrees/dev/examples.doctree b/0.7.0/.doctrees/dev/examples.doctree new file mode 100644 index 0000000..2f9d287 Binary files /dev/null and b/0.7.0/.doctrees/dev/examples.doctree differ diff --git a/0.7.0/.doctrees/dev/faq.doctree b/0.7.0/.doctrees/dev/faq.doctree new file mode 100644 index 0000000..245e29e Binary files /dev/null and b/0.7.0/.doctrees/dev/faq.doctree differ diff --git a/0.7.0/.doctrees/dev/introduction.doctree b/0.7.0/.doctrees/dev/introduction.doctree new file mode 100644 index 0000000..05839fa Binary files /dev/null and b/0.7.0/.doctrees/dev/introduction.doctree differ diff --git a/0.7.0/.doctrees/dev/java.doctree b/0.7.0/.doctrees/dev/java.doctree new file mode 100644 index 0000000..7ed84a0 Binary files /dev/null and b/0.7.0/.doctrees/dev/java.doctree differ diff --git a/0.7.0/.doctrees/dev/java/api_changelog.doctree b/0.7.0/.doctrees/dev/java/api_changelog.doctree new file mode 100644 index 0000000..bf6c0a2 Binary files /dev/null and b/0.7.0/.doctrees/dev/java/api_changelog.doctree differ diff --git a/0.7.0/.doctrees/dev/java/debugging.doctree b/0.7.0/.doctrees/dev/java/debugging.doctree new file mode 100644 index 0000000..7871b2f Binary files /dev/null and b/0.7.0/.doctrees/dev/java/debugging.doctree differ diff --git a/0.7.0/.doctrees/dev/java/javadoc.doctree b/0.7.0/.doctrees/dev/java/javadoc.doctree new file mode 100644 index 0000000..a0307de Binary files /dev/null and b/0.7.0/.doctrees/dev/java/javadoc.doctree differ diff --git a/0.7.0/.doctrees/dev/java/packaging.doctree b/0.7.0/.doctrees/dev/java/packaging.doctree new file mode 100644 index 0000000..b8e872c Binary files /dev/null and b/0.7.0/.doctrees/dev/java/packaging.doctree differ diff --git a/0.7.0/.doctrees/dev/java/prerequisites.doctree b/0.7.0/.doctrees/dev/java/prerequisites.doctree new file mode 100644 index 0000000..f3b2874 Binary files /dev/null and b/0.7.0/.doctrees/dev/java/prerequisites.doctree differ diff --git a/0.7.0/.doctrees/dev/java/snippets.doctree b/0.7.0/.doctrees/dev/java/snippets.doctree new file mode 100644 index 0000000..66e7e10 Binary files /dev/null and b/0.7.0/.doctrees/dev/java/snippets.doctree differ diff --git a/0.7.0/.doctrees/dev/java/testing.doctree b/0.7.0/.doctrees/dev/java/testing.doctree new file mode 100644 index 0000000..dac9e45 Binary files /dev/null and b/0.7.0/.doctrees/dev/java/testing.doctree differ diff --git a/0.7.0/.doctrees/dev/python.doctree b/0.7.0/.doctrees/dev/python.doctree new file mode 100644 index 0000000..2362d20 Binary files /dev/null and b/0.7.0/.doctrees/dev/python.doctree differ diff --git a/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.data_context.doctree b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.data_context.doctree new file mode 100644 index 0000000..f8ebc9f Binary files /dev/null and b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.data_context.doctree differ diff --git a/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.doctree b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.doctree new file mode 100644 index 0000000..4b1d25d Binary files /dev/null and b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.doctree differ diff --git a/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.extraction_plugin.doctree b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.extraction_plugin.doctree new file mode 100644 index 0000000..f3877e8 Binary files /dev/null and b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.extraction_plugin.doctree differ diff --git a/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.extraction_trace.doctree b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.extraction_trace.doctree new file mode 100644 index 0000000..cfb1152 Binary files /dev/null and b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.extraction_trace.doctree differ diff --git a/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.plugin_info.doctree b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.plugin_info.doctree new file mode 100644 index 0000000..885eb87 Binary files /dev/null and b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.plugin_info.doctree differ diff --git a/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.search_result.doctree b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.search_result.doctree new file mode 100644 index 0000000..afce44a Binary files /dev/null and b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.search_result.doctree differ diff --git a/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.trace_searcher.doctree b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.trace_searcher.doctree new file mode 100644 index 0000000..2fac318 Binary files /dev/null and b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.trace_searcher.doctree differ diff --git a/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.tracelet.doctree b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.tracelet.doctree new file mode 100644 index 0000000..9f23921 Binary files /dev/null and b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.tracelet.doctree differ diff --git a/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.transformation.doctree b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.transformation.doctree new file mode 100644 index 0000000..4b75a92 Binary files /dev/null and b/0.7.0/.doctrees/dev/python/api/hansken_extraction_plugin.api.transformation.doctree differ diff --git a/0.7.0/.doctrees/dev/python/api_changelog.doctree b/0.7.0/.doctrees/dev/python/api_changelog.doctree new file mode 100644 index 0000000..38b376b Binary files /dev/null and b/0.7.0/.doctrees/dev/python/api_changelog.doctree differ diff --git a/0.7.0/.doctrees/dev/python/debugging.doctree b/0.7.0/.doctrees/dev/python/debugging.doctree new file mode 100644 index 0000000..7a46a11 Binary files /dev/null and b/0.7.0/.doctrees/dev/python/debugging.doctree differ diff --git a/0.7.0/.doctrees/dev/python/getting_started.doctree b/0.7.0/.doctrees/dev/python/getting_started.doctree new file mode 100644 index 0000000..693f3ad Binary files /dev/null and b/0.7.0/.doctrees/dev/python/getting_started.doctree differ diff --git a/0.7.0/.doctrees/dev/python/hanskenpy.doctree b/0.7.0/.doctrees/dev/python/hanskenpy.doctree new file mode 100644 index 0000000..972fdf2 Binary files /dev/null and b/0.7.0/.doctrees/dev/python/hanskenpy.doctree differ diff --git a/0.7.0/.doctrees/dev/python/packaging.doctree b/0.7.0/.doctrees/dev/python/packaging.doctree new file mode 100644 index 0000000..0a5148b Binary files /dev/null and b/0.7.0/.doctrees/dev/python/packaging.doctree differ diff --git a/0.7.0/.doctrees/dev/python/prerequisites.doctree b/0.7.0/.doctrees/dev/python/prerequisites.doctree new file mode 100644 index 0000000..b9a494c Binary files /dev/null and b/0.7.0/.doctrees/dev/python/prerequisites.doctree differ diff --git a/0.7.0/.doctrees/dev/python/snippets.doctree b/0.7.0/.doctrees/dev/python/snippets.doctree new file mode 100644 index 0000000..97ccecf Binary files /dev/null and b/0.7.0/.doctrees/dev/python/snippets.doctree differ diff --git a/0.7.0/.doctrees/dev/python/testing.doctree b/0.7.0/.doctrees/dev/python/testing.doctree new file mode 100644 index 0000000..d412445 Binary files /dev/null and b/0.7.0/.doctrees/dev/python/testing.doctree differ diff --git a/0.7.0/.doctrees/dev/spec.doctree b/0.7.0/.doctrees/dev/spec.doctree new file mode 100644 index 0000000..66cd297 Binary files /dev/null and b/0.7.0/.doctrees/dev/spec.doctree differ diff --git a/0.7.0/.doctrees/environment.pickle b/0.7.0/.doctrees/environment.pickle new file mode 100644 index 0000000..c5b9d84 Binary files /dev/null and b/0.7.0/.doctrees/environment.pickle differ diff --git a/0.7.0/.doctrees/index.doctree b/0.7.0/.doctrees/index.doctree new file mode 100644 index 0000000..865c216 Binary files /dev/null and b/0.7.0/.doctrees/index.doctree differ diff --git a/0.7.0/.nojekyll b/0.7.0/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/0.7.0/_images/1_debug_start_plugin_in_ide.png b/0.7.0/_images/1_debug_start_plugin_in_ide.png new file mode 100644 index 0000000..15ee2ec Binary files /dev/null and b/0.7.0/_images/1_debug_start_plugin_in_ide.png differ diff --git a/0.7.0/_images/2_debug_set_breakpoints.png b/0.7.0/_images/2_debug_set_breakpoints.png new file mode 100644 index 0000000..0b390ea Binary files /dev/null and b/0.7.0/_images/2_debug_set_breakpoints.png differ diff --git a/0.7.0/_images/3_debug_prepare_extraction.png b/0.7.0/_images/3_debug_prepare_extraction.png new file mode 100644 index 0000000..6d795bb Binary files /dev/null and b/0.7.0/_images/3_debug_prepare_extraction.png differ diff --git a/0.7.0/_images/4_debug_enable_debug_tool.png b/0.7.0/_images/4_debug_enable_debug_tool.png new file mode 100644 index 0000000..279e75a Binary files /dev/null and b/0.7.0/_images/4_debug_enable_debug_tool.png differ diff --git a/0.7.0/_images/cartoon.png b/0.7.0/_images/cartoon.png new file mode 100644 index 0000000..ceae6f5 Binary files /dev/null and b/0.7.0/_images/cartoon.png differ diff --git a/0.7.0/_images/datatransformations.png b/0.7.0/_images/datatransformations.png new file mode 100644 index 0000000..7acefa7 Binary files /dev/null and b/0.7.0/_images/datatransformations.png differ diff --git a/0.7.0/_images/download_template.png b/0.7.0/_images/download_template.png new file mode 100644 index 0000000..81e8adf Binary files /dev/null and b/0.7.0/_images/download_template.png differ diff --git a/0.7.0/_images/hanskenpy_gatekeeper_keystore.png b/0.7.0/_images/hanskenpy_gatekeeper_keystore.png new file mode 100644 index 0000000..849851f Binary files /dev/null and b/0.7.0/_images/hanskenpy_gatekeeper_keystore.png differ diff --git a/0.7.0/_images/hanskenpy_save_query.png b/0.7.0/_images/hanskenpy_save_query.png new file mode 100644 index 0000000..407245c Binary files /dev/null and b/0.7.0/_images/hanskenpy_save_query.png differ diff --git a/0.7.0/_images/pycharm_open_project.png b/0.7.0/_images/pycharm_open_project.png new file mode 100644 index 0000000..58ac2d7 Binary files /dev/null and b/0.7.0/_images/pycharm_open_project.png differ diff --git a/0.7.0/_images/pycharm_open_project_venv.png b/0.7.0/_images/pycharm_open_project_venv.png new file mode 100644 index 0000000..1f9399c Binary files /dev/null and b/0.7.0/_images/pycharm_open_project_venv.png differ diff --git a/0.7.0/_images/pycharm_project_start_page.png b/0.7.0/_images/pycharm_project_start_page.png new file mode 100644 index 0000000..878af64 Binary files /dev/null and b/0.7.0/_images/pycharm_project_start_page.png differ diff --git a/0.7.0/_images/toolrun_error.png b/0.7.0/_images/toolrun_error.png new file mode 100644 index 0000000..8f9d7df Binary files /dev/null and b/0.7.0/_images/toolrun_error.png differ diff --git a/0.7.0/_images/trace.svg b/0.7.0/_images/trace.svg new file mode 100644 index 0000000..89f4289 --- /dev/null +++ b/0.7.0/_images/trace.svg @@ -0,0 +1,40 @@ +TraceString idString nameString[] pathMap<Type, Properties> propertiesList<DataStream>List<Trace> childrenDataStreamString typeString lengthbyte[] dataString fileTypeProperties propertiesa trace could have child tracesa trace could have data streams \ No newline at end of file diff --git a/0.7.0/_modules/hansken_extraction_plugin/api/data_context.html b/0.7.0/_modules/hansken_extraction_plugin/api/data_context.html new file mode 100644 index 0000000..843bd04 --- /dev/null +++ b/0.7.0/_modules/hansken_extraction_plugin/api/data_context.html @@ -0,0 +1,130 @@ + + + + + + hansken_extraction_plugin.api.data_context — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for hansken_extraction_plugin.api.data_context

+"""This module contains the definition of the DataContext."""
+from dataclasses import dataclass
+
+
+
[docs]@dataclass(frozen=True) +class DataContext: + """This class contains the data context of a plugin that is processing a trace.""" + + data_type: str #: the named data type that is being processed + data_size: int #: the size / total length of the data stream that is being processed + + def __eq__(self, other): + # override the default equality check, a subclass is considered equal as long as it matches all the fields this + # type describes + return isinstance(other, DataContext) and (self.data_type, self.data_size) == (other.data_type, other.data_size)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/0.7.0/_modules/hansken_extraction_plugin/api/extraction_plugin.html b/0.7.0/_modules/hansken_extraction_plugin/api/extraction_plugin.html new file mode 100644 index 0000000..a80483e --- /dev/null +++ b/0.7.0/_modules/hansken_extraction_plugin/api/extraction_plugin.html @@ -0,0 +1,183 @@ + + + + + + hansken_extraction_plugin.api.extraction_plugin — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for hansken_extraction_plugin.api.extraction_plugin

+"""
+This module contains the different types of Extraction Plugins.
+
+The types of Extraction Plugins differ in their process functions.
+"""
+from abc import ABC, abstractmethod
+
+from hansken_extraction_plugin.api.data_context import DataContext
+from hansken_extraction_plugin.api.extraction_trace import ExtractionTrace, MetaExtractionTrace
+from hansken_extraction_plugin.api.plugin_info import PluginInfo
+from hansken_extraction_plugin.api.trace_searcher import TraceSearcher
+
+
+
[docs]class BaseExtractionPlugin(ABC): + """All Extraction Plugins are derived from this class.""" + +
[docs] @abstractmethod + def plugin_info(self) -> PluginInfo: + """Return information about this extraction plugin."""
+ + +
[docs]class ExtractionPlugin(BaseExtractionPlugin): + """Default extraction plugin, that processes a trace and one of its datastreams.""" + +
[docs] @abstractmethod + def process(self, trace: ExtractionTrace, data_context: DataContext): + """ + Process a given trace. + + This method is called for every trace that is processed by this tool. + + :param trace: Trace that is being processed + :param data_context: Data data_context describing the data stream that is being processed + """
+ + +
[docs]class MetaExtractionPlugin(BaseExtractionPlugin): + """Extraction Plugin that processes a trace only with its metadata, without processing its data.""" + +
[docs] @abstractmethod + def process(self, trace: MetaExtractionTrace): + """ + Process a given trace. + + This method is called for every trace that is processed by this tool. + + :param trace: Trace that is being processed + """
+ + +
[docs]class DeferredExtractionPlugin(BaseExtractionPlugin): + """ + Extraction Plugin that can be run at a different extraction stage. + + This type of plugin also allows accessing other traces using the searcher. + """ + +
[docs] @abstractmethod + def process(self, trace: ExtractionTrace, data_context: DataContext, searcher: TraceSearcher): + """ + Process a given trace. + + This method is called for every trace that is processed by this tool. + + :param trace: Trace that is being processed + :param data_context: Data data_context describing the data stream that is being processed + :param searcher: TraceSearcher that can be used to obtain more traces + """
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/0.7.0/_modules/hansken_extraction_plugin/api/extraction_trace.html b/0.7.0/_modules/hansken_extraction_plugin/api/extraction_trace.html new file mode 100644 index 0000000..ada8209 --- /dev/null +++ b/0.7.0/_modules/hansken_extraction_plugin/api/extraction_trace.html @@ -0,0 +1,331 @@ + + + + + + hansken_extraction_plugin.api.extraction_trace — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for hansken_extraction_plugin.api.extraction_trace

+"""
+This module contains the different Trace apis.
+
+Note that there are a couple of different traces:
+  * The ExtractionTrace and MetaExtractionTrace, which are offered to the process function.
+  * ExtractionTraceBuilder, which is a trace that can be built; it does not exist in hansken yet, but it is added after
+    building.
+  * SearchTrace, which represents an immutable trace which is returned after searching for traces.
+"""
+from abc import ABC, abstractmethod
+from io import BufferedReader, BufferedWriter
+from typing import Any, Literal, Mapping, Optional, Union
+
+from hansken_extraction_plugin.api.tracelet import Tracelet
+from hansken_extraction_plugin.api.transformation import Transformation
+
+
+
[docs]class ExtractionTraceBuilder(ABC): + """ + ExtractionTrace that can be build. + + Represents child traces. + """ + +
[docs] @abstractmethod + def update(self, key_or_updates: Union[Mapping, str] = None, value: Any = None, + data: Mapping[str, bytes] = None) -> 'ExtractionTraceBuilder': + """ + Update or add metadata properties for this `.ExtractionTraceBuilder`. + + Can be used to update the name of the Trace represented by this builder, + if not already set. + + :param key_or_updates: either a `str` (the metadata property to be + updated) or a mapping supplying both keys and values to be updated + :param value: the value to update metadata property *key* to (used + only when *key_or_updates* is a `str`, an exception will be thrown + if *key_or_updates* is a mapping) + :param data: a `dict` mapping data type / stream name to bytes to be + added to the trace + :return: this `.ExtractionTraceBuilder` + """
+ +
[docs] @abstractmethod + def add_tracelet(self, + tracelet: Union[Tracelet, str], + value: Optional[Mapping[str, Any]] = None) -> 'ExtractionTraceBuilder': + """ + Add a `.Tracelet` to this `.ExtractionTraceBuilder`. + + :param tracelet: the Tracelet or tracelet type (supplied as a `str`) to add + :param value: the tracelet properties to add (only applicable when *tracelet* is a `str`) + :return: this `.ExtractionTraceBuilder` + """
+ +
[docs] @abstractmethod + def add_transformation(self, data_type: str, transformation: Transformation) -> 'ExtractionTraceBuilder': + """ + Update or add transformations for this `.ExtractionTraceBuilder`. + + :param data_type: data type of the Transformation + :param transformation: the Transformation to add + :return: this `.ExtractionTraceBuilder` + """
+ +
[docs] @abstractmethod + def child_builder(self, name: str = None) -> 'ExtractionTraceBuilder': + """ + Create a new `.TraceBuilder` to build a child trace to the trace to be represented by this builder. + + .. note:: + Traces should be created and built in depth first order, + parent before child (pre-order). + + :return: a `.TraceBuilder` set up to save a new trace as the child + trace of this builder + """
+ +
[docs] def add_data(self, stream: str, data: bytes) -> 'ExtractionTraceBuilder': + """ + Add data to this trace as a named stream. + + :param stream: name of the data stream to be added + :param data: data to be attached + :return: this `.ExtractionTraceBuilder` + """ + return self.update(data={stream: data})
+ +
[docs] @abstractmethod + def open(self, data_type: str = None, offset: int = 0, size: int = None, mode: Literal['rb', 'wb'] = 'rb') \ + -> Union[BufferedReader, BufferedWriter]: + """ + Open a data stream to read or write data from or to the `.ExtractionTrace`. + + :param data_type: the data type of the datastream, 'raw' by default + :param offset: byte offset to start the stream on when reading + :param size: the number of bytes to make available when reading + :param mode: 'rb' for reading, 'wb' for writing + :return: a file-like object to read or write bytes from the named stream + """
+ +
[docs] @abstractmethod + def build(self) -> str: + """ + Save the trace being built by this builder to remote. + + .. note:: + Building more than once will result in an error being raised. + + :return: the new trace' id + """
+ + +
[docs]class Trace(ABC): + """All trace classes should be able to return values.""" + +
[docs] @abstractmethod + def get(self, key: str, default: Any = None) -> Any: + """ + Return metadata properties for this `.ExtractionTrace`. + + :param key: the metadata property to be retrieved + :param default: value returned if property is not set + :return: the value of the requested metadata property + """
+ + +
[docs]class SearchTrace(Trace): + """SearchTraces represent traces returned when searching for traces.""" + +
[docs] @abstractmethod + def open(self, stream: str = 'raw', offset: int = 0, size: int = None) -> BufferedReader: + """ + Open a data stream of the data that is being processed. + + :param stream: data stream of trace to open. defaults to raw. other examples are html, text, etc. + :param offset: byte offset to start the stream on + :param size: the number of bytes to make available + :return: a file-like object to read bytes from the named stream + """
+ + +
[docs]class MetaExtractionTrace(Trace): + """ + MetaExtractionTraces contain only metadata. + + This class represenst traces during the extraction of an extraction plugin without a data stream. + """ + +
[docs] @abstractmethod + def update(self, key_or_updates: Union[Mapping, str] = None, value: Any = None, + data: Mapping[str, bytes] = None) -> None: + """ + Update or add metadata properties for this `.ExtractionTrace`. + + :param key_or_updates: either a `str` (the metadata property to be + updated) or a mapping supplying both keys and values to be updated + :param value: the value to update metadata property *key* to (used + only when *key_or_updates* is a `str`, an exception will be thrown + if *key_or_updates* is a mapping) + :param data: a `dict` mapping data type / stream name to bytes to be + added to the trace + """
+ +
[docs] @abstractmethod + def add_tracelet(self, + tracelet: Union[Tracelet, str], + value: Optional[Mapping[str, Any]] = None) -> None: + """ + Add a `.Tracelet` to this `.MetaExtractionTrace`. + + :param tracelet: the Tracelet or tracelet type to add + :param value: the tracelet properties to add (only applicable when *tracelet* is a tracelet type) + """
+ +
[docs] @abstractmethod + def add_transformation(self, data_type: str, transformation: Transformation) -> None: + """ + Update or add transformations for this `.ExtractionTraceBuilder`. + + :param data_type: data type of the Transformation + :param transformation: the Transformation to add + """
+ +
[docs] @abstractmethod + def child_builder(self, name: str = None) -> ExtractionTraceBuilder: + """ + Create a `.TraceBuilder` to build a trace to be saved as a child of this `.Trace`. + + A new trace will only be added to the index once explicitly saved (e.g. + through `.TraceBuilder.build`). + + .. note:: + Traces should be created and built in depth first order, + parent before child (pre-order). + + :param name: the name for the trace being built + :return: a `.TraceBuilder` set up to create a child trace of this `.MetaExtractionTrace` + """
+ + +
[docs]class ExtractionTrace(MetaExtractionTrace): + """Trace offered to be processed.""" + +
[docs] @abstractmethod + def open(self, data_type: str = None, offset: int = 0, size: int = None, mode: Literal['rb', 'wb'] = 'rb') \ + -> Union[BufferedReader, BufferedWriter]: + """ + Open a data stream to read or write data from or to the `.ExtractionTrace`. + + :param data_type: the data type of the datastream, 'raw' by default + :param offset: byte offset to start the stream on when reading + :param size: the number of bytes to make available when reading + :param mode: 'rb' for reading, 'wb' for writing + :return: a file-like object to read or write bytes from the named stream + """
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/0.7.0/_modules/hansken_extraction_plugin/api/plugin_info.html b/0.7.0/_modules/hansken_extraction_plugin/api/plugin_info.html new file mode 100644 index 0000000..06f381c --- /dev/null +++ b/0.7.0/_modules/hansken_extraction_plugin/api/plugin_info.html @@ -0,0 +1,197 @@ + + + + + + hansken_extraction_plugin.api.plugin_info — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for hansken_extraction_plugin.api.plugin_info

+"""This module contains all definitions to describe meta data of a plugin, a.k.a. PluginInfo."""
+from dataclasses import dataclass
+from enum import Enum
+from typing import Optional
+
+
+
[docs]@dataclass(frozen=True) +class Author: + """ + The author of an Extraction Plugin. + + This information can be retrieved by an end-user from Hansken. + """ + + name: str + email: str + organisation: str
+ + +
[docs]class MaturityLevel(Enum): + """This class represents the maturity level of an extraction plugin.""" + + PROOF_OF_CONCEPT = 0 + READY_FOR_TEST = 1 + PRODUCTION_READY = 2
+ + +
[docs]@dataclass(frozen=True) +class PluginId: + """Identifier of a plugin, consisting of domain, category and name. Needs to be unique among all tools/plugins.""" + + domain: str + category: str + name: str + + def __str__(self): + return f'{self.domain}/{self.category}/{self.name}'.lower()
+ + +
[docs]@dataclass(frozen=True) +class PluginResources: + """PluginResources contains information about how many resources will be used for a plugin.""" + + maximum_cpu: Optional[float] + """ + CPU resources are measured in cpu units. One cpu is equivalent to 1 vCPU/Core for cloud providers and 1 hyperthread + on bare-metal Intel processors. Also, fractional requests are allowed. A plugin that asks 0.5 CPU uses half as + much CPU as one that asks for 1 CPU. + """ + maximum_memory: Optional[int] + """Max usable memory for a plugin, measured in megabytes.""" + + def __post_init__(self): + if self.maximum_cpu is not None and self.maximum_cpu < 0: + raise ValueError(f'maximum_cpu cannot be < 0: {self.maximum_cpu}') + if self.maximum_memory is not None and self.maximum_memory < 0: + raise ValueError(f'maximum_memory cannot be < 0: {self.maximum_memory}')
+ + +
[docs]@dataclass +class PluginInfo: + """ + This information is used by Hansken to identify and run the plugin. + + Note that the build_plugin.py build script is used to build a plugin docker image with PluginInfo docker labels. + """ + + id: PluginId #: a plugin's unique identifier, see PluginId + version: str #: version of the plugin + description: str #: short description of the functionality of the plugin + author: Author #: the plugin's author, see Author + maturity: MaturityLevel #: maturity level, see MaturityLevel + matcher: str #: this matcher selects the traces offered to the plugin + webpage_url: str #: plugin url + license: Optional[str] = None #: license of this plugin + deferred_iterations: int = 1 #: number of deferred iterations (1 to 20), nly for deferred plugins (optional) + resources: Optional[PluginResources] = None #: resources to be reserved for a plugin (optional) + + def __post_init__(self): + if not 1 <= self.deferred_iterations <= 20: + raise ValueError(f'Invalid value for deferred_iterations: {self.deferred_iterations}. ' + f'Valid values are 1 =< 20.')
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/0.7.0/_modules/hansken_extraction_plugin/api/search_result.html b/0.7.0/_modules/hansken_extraction_plugin/api/search_result.html new file mode 100644 index 0000000..53fd5d9 --- /dev/null +++ b/0.7.0/_modules/hansken_extraction_plugin/api/search_result.html @@ -0,0 +1,187 @@ + + + + + + hansken_extraction_plugin.api.search_result — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for hansken_extraction_plugin.api.search_result

+"""This module contains a representation of a search result."""
+from abc import ABC, abstractmethod
+from itertools import islice
+from typing import Iterable, List, Optional
+
+from hansken_extraction_plugin.api.extraction_trace import SearchTrace
+
+
+
[docs]class SearchResult(ABC, Iterable): + """ + Class representing a stream of traces, returned when performing a search request. + + This result can only be iterated once. Results can be retrieved in three ways: + + Treating the result as an iterable: + + .. code-block:: python + + for trace in result: + print(trace.name) + + Calling `.take` to process one or more batches of traces: + + .. code-block:: python + + first_100 = result.take(100) + process_batch(first_100) + + Calling `.takeone` to get a single trace: + + .. code-block:: python + + first = result.takeone() + second = result.takeone() + + print(first.name, second.name) + + """ + +
[docs] @abstractmethod + def total_results(self) -> int: + """ + Return the total number of hits. + + :return: Total number of hits + """ + pass
+ +
[docs] def takeone(self) -> Optional[SearchTrace]: + """ + Return a single trace, if this stream is not exhausted. + + :return: A searchtrace, or None if no trace is available + """ + return next(self.__iter__(), None)
+ +
[docs] def take(self, num: int) -> List[SearchTrace]: + """ + Return a list containing at most num number of traces, or less if they are not available. + + :param num: Number of traces to take + :return: List containing zero or more traces + """ + return list(islice(self.__iter__(), num))
+ +
[docs] def close(self): + """ + Close this SearchResult if no more traces are to be retrieved. + + Required to keep compatibility with hansken.py. + """ + pass
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/0.7.0/_modules/hansken_extraction_plugin/api/trace_searcher.html b/0.7.0/_modules/hansken_extraction_plugin/api/trace_searcher.html new file mode 100644 index 0000000..9129637 --- /dev/null +++ b/0.7.0/_modules/hansken_extraction_plugin/api/trace_searcher.html @@ -0,0 +1,133 @@ + + + + + + hansken_extraction_plugin.api.trace_searcher — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for hansken_extraction_plugin.api.trace_searcher

+"""This module contains the definition of a trace searcher."""
+from abc import abstractmethod
+
+from hansken_extraction_plugin.api.search_result import SearchResult
+
+
+
[docs]class TraceSearcher: + """This class can be used to search for traces, using the search method.""" + +
[docs] @abstractmethod + def search(self, query: str, count: int) -> SearchResult: + """ + Search for indexed traces in Hansken using provided query returning at most count results. + + :param query: HQL-query used for searching + :param count: Maximum number of traces to return + :return: SearchResult containing found traces + """
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/0.7.0/_modules/hansken_extraction_plugin/api/tracelet.html b/0.7.0/_modules/hansken_extraction_plugin/api/tracelet.html new file mode 100644 index 0000000..e4aa32a --- /dev/null +++ b/0.7.0/_modules/hansken_extraction_plugin/api/tracelet.html @@ -0,0 +1,143 @@ + + + + + + hansken_extraction_plugin.api.tracelet — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for hansken_extraction_plugin.api.tracelet

+"""This module contains the definition of a Tracelet."""
+from typing import Any, Mapping
+
+
+
[docs]class Tracelet: + """ + A tracelet contains the values of a single fvt (Few Valued Type). + + A few valued type is a trace property type that is a collection of tracelets. A trace can contain multiple few + valued types containing one or more tracelets. For example, the `trace.identity`` type may look like this:: + + {emailAddress: 'interesting@notreally.com'}, + {firstName: 'piet'}, + {emailAddress: 'anotheremail@notreally.com'}, + + The trace.identity few valued types contains three different tracelets. + """ + + def __init__(self, name: str, value: Mapping[str, Any]): + """ + Initialize a tracelet. + + :param name: name or type of the tracelet. In the example this would be ``identity``. + :param value: Mapping of keys to properties of this tracelet. In the example this could be + ``{emailAddress: 'anotheremail@notreally.com'}``. + """ + self.name = name + self.value = value
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/0.7.0/_modules/hansken_extraction_plugin/api/transformation.html b/0.7.0/_modules/hansken_extraction_plugin/api/transformation.html new file mode 100644 index 0000000..4234add --- /dev/null +++ b/0.7.0/_modules/hansken_extraction_plugin/api/transformation.html @@ -0,0 +1,176 @@ + + + + + + hansken_extraction_plugin.api.transformation — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for hansken_extraction_plugin.api.transformation

+"""This module contains the definition of a Transformation."""
+from abc import ABC
+from dataclasses import dataclass
+from typing import List
+
+
+
[docs]class Transformation(ABC): + """A super class for data transformations. Currently only :class:RangedTransformation is supported.""" + + pass
+ + +
[docs]@dataclass(frozen=True) +class Range: + """A Range describes a range of bytes with a offset and length.""" + + offset: int #: the starting point of the data + length: int #: the size of the data
+ + +
[docs]class RangedTransformation(Transformation): + """A :class:RangedTransformation describes a data transformation consisting of a list of :class:Range.""" + + def __init__(self, ranges: List[Range]): + """:type ranges: list of :class:Range.""" + self.ranges = ranges + +
[docs] @staticmethod + def builder(): + """:return a Builder.""" + return RangedTransformation.Builder()
+ +
[docs] class Builder: + """Helper class that implements a transformation builder.""" + + def __init__(self): + """Initialize a Builder.""" + self._ranges: List[Range] = [] + +
[docs] def add_range(self, offset: int, length: int) -> 'RangedTransformation.Builder': + """ + Add a range to a ranged transformation by providing the range's offset and length. + + :param offset the offset of the data transformation + :param length the length of the data transformation + :return: this `.RangedTransformation.Builder` + """ + if offset is None: + raise ValueError('offset is required') + if length is None: + raise ValueError('length is required') + self._ranges.append(Range(offset=offset, length=length)) + return self
+ +
[docs] def build(self) -> 'RangedTransformation': + """ + Return a RangedTransformation. + + :return: a :class:RangedTransformation + """ + return RangedTransformation(ranges=self._ranges)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/0.7.0/_modules/index.html b/0.7.0/_modules/index.html new file mode 100644 index 0000000..b8adeaa --- /dev/null +++ b/0.7.0/_modules/index.html @@ -0,0 +1,122 @@ + + + + + + Overview: module code — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + +
+ + +
+ + +
+
+ + + + \ No newline at end of file diff --git a/0.7.0/_sources/changes.rst.txt b/0.7.0/_sources/changes.rst.txt new file mode 100644 index 0000000..3277514 --- /dev/null +++ b/0.7.0/_sources/changes.rst.txt @@ -0,0 +1,15 @@ +.. _changelog: + +Changelog +========= + +The following page lists all (technical) changes in the extraction plugin SDK. + +Programming language specific API changes are described in more detail on API changelog pages. +These pages list new API functionalities, and describe how to update your plugins when API changes are in order. +For the API changelog pages see: + +* :doc:`dev/java/api_changelog` +* :doc:`dev/python/api_changelog` + +.. mdinclude:: ../CHANGES.md diff --git a/0.7.0/_sources/contact.md.txt b/0.7.0/_sources/contact.md.txt new file mode 100644 index 0000000..073d2ad --- /dev/null +++ b/0.7.0/_sources/contact.md.txt @@ -0,0 +1,16 @@ +# Contact + +Please get in touch with us: + +* if you have questions about the SDK, +* have found a bug, +* have a feature request, +* see other opportunity to improve, +* want to contribute, +* ... + +Chat with us on [Discord](https://www.discord.com). You can find members of the extraction plugin SDK development team +in the Hansken Community server, in the `extraction-plugins` channel. This is a Hansken community-private server. If you +don't have access to the Hansken Community server, please contact your Hansken business owner. He or she can invite you +to the Hansken Community server. If you don't know who to contact, feel free to fill in +the [contact form](https://www.hansken.nl/contact) for further questions/contact. diff --git a/0.7.0/_sources/dev/concepts.rst.txt b/0.7.0/_sources/dev/concepts.rst.txt new file mode 100644 index 0000000..7348949 --- /dev/null +++ b/0.7.0/_sources/dev/concepts.rst.txt @@ -0,0 +1,21 @@ +General concepts +================ + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + concepts/extraction_plugins + concepts/anatomy_of_a_plugin + concepts/plugin_types + concepts/plugin_naming_convention + concepts/traces + concepts/hql_lite + concepts/data_transformations + + concepts/test_framework + concepts/all_in_one_debugging + + concepts/isolation + concepts/kubernetes_autoscaling + diff --git a/0.7.0/_sources/dev/concepts/all_in_one_debugging.md.txt b/0.7.0/_sources/dev/concepts/all_in_one_debugging.md.txt new file mode 100644 index 0000000..b73b068 --- /dev/null +++ b/0.7.0/_sources/dev/concepts/all_in_one_debugging.md.txt @@ -0,0 +1,32 @@ +# Debugging locally with Hansken All in One (AIO) + +.. note:: this feature is available from Hansken 46.4.0 + +.. warning:: `DeferredExtractionPlugin` are currently NOT supported + +It is also possible to debug an Extraction Plugin with a locally running Hansken AIO. This requires a few steps: + +1. Start your plugin in your IDE (default port 8999) + ![1_debug_start_plugin_in_ide.png](1_debug_start_plugin_in_ide.png) +2. Set some breakpoint(s) + ![2_debug_set_breakpoints.png](2_debug_set_breakpoints.png) +3. Prepare an extraction in the AIO **with advanced** options + ![3_debug_prepare_extraction.png](3_debug_prepare_extraction.png) +4. Enable the `DebugExtractionPluginTool` for this extraction + ![4_debug_enable_debug_tool.png](4_debug_enable_debug_tool.png) +5. Start extraction, and happy debugging! + +### Tips/notes: + +- You can only debug 1 plugin at a time + - .. note:: If you are debugging `MyPlugin`, and it is also visible in the tools list, then you need to **disable** it, and **only enable** the `DebugExtractionPluginTool` +- Test your plugin with a small image. Otherwise, it might take long before you reach your breakpoint. +- Be careful when using this debugger with `APPEND` extractions: + - Similar to other tools/plugins, the `DebugExtractionPluginTool` will only run once per trace + - So if you need to re-run your debug session, then we advise you to re-extract `(INDEX)` your project instead +- Hansken runs multiple instances of every Tool, so the same breakpoint can be hit multiple times concurrently by + different instances + - .. note:: `UNSUPPORTED:` it is currently not supported to limit the number of plugin threads/workers +- Only restart your plugin before starting an extraction. Restarting your plugin **during** an extraction can + produce + **undefined behaviour**. \ No newline at end of file diff --git a/0.7.0/_sources/dev/concepts/anatomy_of_a_plugin.md.txt b/0.7.0/_sources/dev/concepts/anatomy_of_a_plugin.md.txt new file mode 100644 index 0000000..d17f7ae --- /dev/null +++ b/0.7.0/_sources/dev/concepts/anatomy_of_a_plugin.md.txt @@ -0,0 +1,72 @@ +# Anatomy of a plugin + +This page describes the general anatomy of a plugin, and its (simplified) execution in Hansken. + +## The plugin itself + +Each plugin must implement two methods: + +* `pluginInfo()`: This method returns information about the plugin. +* `process()`: This method performs the extraction task of the plugin. + +Lets dive a bit deeper into these methods in the next sections! + +### The method `pluginInfo()` + +The `pluginInfo()` method returns a `PluginInfo` object. Hansken needs this object to be able to know the capabilities +of the plugin, and to show the plugin in the list of tools. The most important fields that must be set on `PluginInfo` +are the following: + +| `id` | The identifier of the plugin. This will be used as a unique name for the plugin Hansken. | +| `description` | A description of the plugin that is shown in Hansken. | +| `author` | The author of the plugin that is shown in Hansken. | +| `license` | The type of license of the plugin that is shown in Hansken. | +| `matcher` | This matcher is used by Hansken to determine which Traces are sent to the Plugin during extraction. | + +### The method `process()` + +During extraction, Hansken calls the `process()` method for every matching trace. The `matcher` attribute of +the `PluginInfo` is very important as it determines which traces will be sent to the `process` method. + +Although the plugin developer is free to program whatever seems useful, the following tasks are typically performed +within the `process()` method: + +* Creating child-traces +* Reading trace properties +* Adding trace properties +* Reading the data that the Trace represents +* Writing data on a Trace + +Depending on the type of plugin that is implemented, different functionality is available in the `process()` method. +See [Plugin Types](plugin_types.md) for more details. + +## The execution in Hansken + +This describes the process of running a plugin from the perspective of Hansken. The perspective of the user is described +in [Hansken Extraction Plugins](extraction_plugins.md). + +### Plugin discovery + +Hansken manages a list of tools that can be used in extractions. The available plugins must be added to this list so +that the user can select them. To accomplish this, Hansken scans the Docker registry for docker images that are plugins. +Each image is started up, and a call is done to its `pluginInfo()` method. If the call resulted in a valid `PluginInfo` +object, the Extraction Plugin is added to the list of tools visible to users. After the `PluginInfo` is retrieved, the +docker image is shutdown again. + +### Starting an extraction + +Hansken checks if any plugins are selected by the user that started the extraction. For each selected plugin, at least +one docker image will be started. See [Kubernetes autoscaling](kubernetes_autoscaling.md) for more details on expanding +the number of instances for each plugin. + +### Extracting + +During an extraction, Hansken will iteratively loop through all selected tools, including Extraction Plugins. For each +trace that matches on a tool, Hansken will call its `process` method. For Extraction Plugins, this means that +the `process` method is called via the gRPC protocol. The trace to be processed is sent over gRPC to the plugin, and any +other communication between Hansken and the Extraction Plugin (like created properties and child traces, search requests +and written data) are done using gRPC. + +### Finishing an extraction + +At the end of an extraction, Hansken will stop all associated plugins. diff --git a/0.7.0/_sources/dev/concepts/data_transformations.md.txt b/0.7.0/_sources/dev/concepts/data_transformations.md.txt new file mode 100644 index 0000000..116c0a5 --- /dev/null +++ b/0.7.0/_sources/dev/concepts/data_transformations.md.txt @@ -0,0 +1,24 @@ +# Data Transformations + +Extraction Plugins can create new :ref:`data-streams ` on a Trace through data transformations. Data +transformations describe how data can be obtained from a source. Data transformations are preferred over storing blobs +because they take less space. This is because they only describe the data instead of specifying the actual data. + +The following figure shows how Hansken visualizes data transformations: +![datatransformations.png](datatransformations.png) +Note that transformations can be applied on transformations. The SDK only supports range transformations at the moment, +while this image also shows some transformations that are currently available in Hansken but not in the SDK. + +An example case is an extraction plugin that processes an archive file. The plugin creates a child trace per entry in +the archive file. Each child trace will have a data stream that is a transformation that marks the start and length of +the entry in the original archive data. By just describing the data instead of specifying the actual data, a lot of +space is saved. + +Although Hansken supports various transformations, the Extraction Plugins SDK for now only supports ranged data +transformations. Ranged data transformations define data as a list of ranges, each range with an offset and length in a +bytearray. + +## See also + +* [Data Transformations in Java](../java/snippets.md#Data Transformations) +* [Data Transformations in Python](../python/snippets.md#Data Transformations) diff --git a/0.7.0/_sources/dev/concepts/extraction_plugins.md.txt b/0.7.0/_sources/dev/concepts/extraction_plugins.md.txt new file mode 100644 index 0000000..74246e9 --- /dev/null +++ b/0.7.0/_sources/dev/concepts/extraction_plugins.md.txt @@ -0,0 +1,60 @@ +# Hansken Extraction Plugins + +Hansken Extraction Plugins enable Hansken users to add their own extraction tools to Hansken. + +To use an Extraction Plugin in Hansken, the following steps have to be done: + +1. Build the plugin +2. Upload the plugin to Hansken +3. Refresh the Hansken tools list +4. Start an extraction with the plugin enabled + +## Building a plugin + +Hansken Extraction Plugins can be built in Java or Python by implementing an interface. Which interface you choose +depends on the type of plugin you choose to make, see [Plugin Types](plugin_types.md). For more information on coding +your own plugin, see the [Extraction Plugin Examples](https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples). + +The plugin can then be tested using the [Test Framework](test_framework.md). This way you make sure everything works as +expected before taking further steps. + +## Package the plugin + +To upload a plugin into Hansken, a docker image for this plugin must be uploaded to the docker registry. First, the +plugin container image must be packaged. +A plugin is packaged into an OCI image (also known as Docker image). + +* [Package a docker image for a Python plugin](../python/packaging.md) +* [Package a docker image for a Java plugin](../java/packaging.md) + + +.. _upload_plugin: + +## Upload the plugin to Hansken + +Hansken finds the plugins by scanning a docker registry. It will try to load all docker images with a certain prefix as +Extraction Plugins. The settings for this are defined in Hansken properties: + +* `registry.extraction.plugins.registry.uri` defines the registry +* `registry.extraction.plugins.registry.prefix` defines the prefix plugins must have + +When the image is packaged locally, it needs to be pushed (uploaded) to the docker registry. These commands provide an +outline of how to do this: + +* `docker login ` (make sure you are logged in to the registry) +* `docker push ` (push the plugin docker image to the registry) + +.. note:: For more information about uploading plugins and running them in Hansken, see the 'Using extraction plugins in + Hansken' chapter of the *Hansken User Guide*. + +## Refresh the Hansken tools list + +Hansken checks which plugins are available at startup. The list of available plugins can also be refreshed by calling +the following endpoint: `/gatekeeper/tools?refresh=true` + +This can be invoked via an internet browser. + +## Start an extraction with the plugin enabled + +If everything went well, the list of available tools in Hansken should now feature your plugin. To run the plugin in an +extraction, be sure to select its checkbox in the extraction tools dialog. diff --git a/0.7.0/_sources/dev/concepts/hql_lite.md.txt b/0.7.0/_sources/dev/concepts/hql_lite.md.txt new file mode 100644 index 0000000..5b4c373 --- /dev/null +++ b/0.7.0/_sources/dev/concepts/hql_lite.md.txt @@ -0,0 +1,375 @@ +# HQL-Lite + +## Overview + +HQL-Lite is a query language derived from Hanskens full HQL human. HQL stands for Hansken Query Language and can be +used to search or match traces. Since not all elements of full HQL can be used in the context of an extraction, +extraction plugins use HQL-Lite, a lightweight version of HQL. This document describes the usage of HQL-Lite in the +context of extraction plugins. + +.. _howdoeshanskenwork: + +## How does Hansken work? + +- Let's say we have a Hansken image `hansken_image1` with 10 pdf files, and 5 jpegs. +- And our Hansken contains 2 tools: + - PdfPlugin + - JpegTool + +.. note:: All plugins are Hansken tools, but not all Hansken tools are plugins. Some tools are included in Hansken core. + +Let's look at a (simplified) pseudocode example of the inner workings of Hansken: + +```python + for each trace in new_traces { + for each datastream in trace { + for each tool in hansken_tools { + if tool.can_this_tool_process_the_provided_trace(trace, datastream) { + tool.process_the_trace(trace, datastream) + } + } + } + } +``` + +So in this example we know the following: + +- `new_traces` has + - 10 pdf files + - 5 jpeg files +- `hansken_tools` contains: + - PdfPlugin + - JpegTool + +So the question here is, how do we prevent that traces are not processed by incompatible tools? + +The answer is the `tool.can_this_tool_process_the_provided_trace()` part of the pseudocode. + +### What does `can_this_tool_process_the_provided_trace()` do? + +Hansken actually contains many more tools/plugins than these 2, and instead of 15 files/traces, we usually deal with +millions. + +.. note:: If each trace has 1 extra second of overhead, 1 million traces would take 11.5 days of extra CPU time + +#### Matchers to the rescue + +To reduce the unnecessary overhead of processing all traces (even the ones the tool cannot actually process), Hansken +implements the concept of a `matcher` for each tool. This _matcher_ basically checks the _trace_ for _"matching +conditions"_, that would allow the tool to process it. + +Sometimes these _matching conditions_ can be as simple as a specific `filename` or `extension`, but are often more +elaborate in the sense that they check multiple factors that require some intimate knowledge of Hansken. + +## What is HQL-Lite? + +HQL-Lite is a language based on HQL (Hansken Query Language) that allows plugin developers to write _matchers_ for +Hansken Extraction Plugins. It could be said that HQL-Lite contains a subset of HQL features, plus some HQL-Lite unique +features that are only interesting for _matchers_. + +.. note:: + Please note that even though the HQL-Lite query is part of the plugin, it is compiled and stored in Hansken during + startup to achieve performance. + +### Why not just use HQL for plugins? + +HQL was designed to search for traces stored in the Elasticsearch database. As such, some of its features are tightly +coupled to the Elasticsearch implementation, making it difficult to re-implement them for plugins. + +Also, even though HQL is more complex than the requirements for _matching_ in plugins, a couple of minor features that +are absolutely necessary for _matching_ are not implemented in HQL, as they don't make much sense from a search point of +view. This is because HQL was designed to be used with _finished extractions_ with all the traces stored in the +database, while HQL-Lite was designed for _active extractions_. + +.. _hqllite syntax: + +### HQL-Lite syntax + +| Matcher | Syntax | remarks | +|----------|------------------------|--------------------------------------------------------------------------------------------------------| +| All | `""` | an empty string translates to match for __all__ traces | +| And | `foo:1 AND bar:2` | the case-sensitive `AND` operator behaves like a logical AND of 2 conditions | +| Not | `NOT foo` or `-foo` | the case-sensitive `NOT` or `-` negates the expression that follows | +| Range | `foo>1` or `1<=foo<10` | a numbered-range check with a min or/and max range(s) | +| Or | `foo:1 OR bar:2` | the case-sensitive `OR` operator behaves like a logical OR of 2 conditions | +| Data | `$data.foo:1` | see `$data` section below | +| DataType | `$data.type:raw` | this query matches against the type of the current datastream | +| Types | `type:email` | this query checks if the trace contains a certain trace type as defined in the Hansken trace model | + +There are also a couple of general guidelines that apply to all matchers: + +- Equals/not equals: + - `:` or `=` : The most basic of left equals right statements. note that `=` is also valid. + - `!=` : The opposite of equals, not equals. Note that `!:` is __NOT__ supported. +- Wildcards: + - `?` : Match against any single character. E.g. `foo:r?w` will match against `raw, row` but not against `rowing`. + - `*` : Match against any chars. E.g. `foo:r*` will match against `r, ra, raw, raaaaaw` but not against `aw`. +- Exact match: By surrounding a value with quotes, we tell the parser that it is a single value. This is especially + helpful for values that might contain separators. E.g. `foo:'hello hql-lite'`. +- CSV: Currently only the `type` query supports multiple values to check against. E.g. `type:email,chatMessage` will only + return `true` if both types exist for this trace. +- `()` grouping: You can group statements by putting brackets around them. E.g. `foo:1 AND (bar:2 OR bla:3)` which + translates to `foo:1` plus one of the statements in the brackets. +- Escaping `\"\\ .\t\r\n:=> this is the regex matcher, which is unsupported in HQL-lite + - `foo:c:\` -> should be `foo:c\:\\`, both the `colon` and the `slash` need to be escaped + - `foo:'c:\'` -> should be `foo:'c:\\'`, the `slash` still needs to be escaped + - .. note:: the backslash is the universal escape character, so it **always** needs to be escaped. + +#### $data matchers + +In Hansken, a trace can have multiple :ref:`datastreams `. The exact content of said datastreams is +discussed elsewhere, but the basic idea is that a trace can have multiple representations. For example, a trace might +have a `raw` datastream, but after we identify that the raw bytes contain a __text__ file, we might add a separate +datastream `text`. + +.. note:: + The `process()` method of each plugin is called for each datastream of each trace. This is explained + in :ref:`How does Hansken work? ` . Subsequently, you might have the same property for a + different datastream. For example: you might have a `data.raw.size` and a `data.text.size` property. The reason you + might have the same property multiple times, is because it could have a different meaning. + +For example: + +- data.raw.size: is the size in bytes +- data.text.size: is the number of bytes in the text representation of the raw stream + +If we want to check if either of these properties is not empty by using a `$data` matcher, we do: + +```text + $data.size>0 +``` + +##### When is it useful to use a $data matcher? + +For example, there is a simple plugin called `LetterCountPlugin`, that counts the letters in text based datastreams. + +So to match on these text based datastreams, we have 2 choices: + +- List all the possibilities + - Which is too tedious, and not very flexible when new types are supported +- Match on a common property + - More compact, but sometimes difficult to find a common property + +In this case we might match on mimeType, which we know is `text/plain` or `text/x-log` for 2 of types we want to match: + +```text + $data.mimeType=text\\/* +``` + +This will match the following: + +- `data.text.mimeType=text\\/plain` +- `data.text.mimeType=text\\/not\\ plain` +- `data.pdf.mimeType=text\\/encoded` +- `data.foo.mimeType=text\\/bar` + +But will __not__ match any of the following: + +- `data.text.mimeType=txt` +- `data.text.mimeType=pdf` +- `data.text.mime=text\\/plain` +- `data.foo.bar=text\\/plain` + +## How to write a matcher? + +The functional requirements for writing a matcher can be summarized in the following: + +1. What does my plugin expect as input? +2. How can I describe that input with the information Hansken provides? + +### PdfPlugin example + +Let's say we just finished writing a `PdfPlugin`. This is a simple plugin that checks if pdf files contain the +word `the`. + +So let's go over our checklist: + +#### _What does my plugin expect as input?_ + +PDF files. + +#### _How can I describe that input with the information Hansken provides?_ + +Hansken consumes and produces :ref:`Traces `. To that effect, we can only match on trace properties that are +available in Hansken. + +##### Match on extension + +The easiest way would be to only allow traces with the `.pdf` extension. Looking at the :ref:`Hansken trace model` (or a +Hansken extraction), we can see that there's a property `file` +which contains a property `extension`. + +So what would that look like in HQL-lite? Something like + +```text + file.extension=pdf +``` + +.. warning:: This of course **only** works if the file has the correct extension (note that matchers are case-sensitive). + +So what do we do, if we also want to match pdf files that are (un)intentionally misnamed? + +##### Match on mime-type + +Looking at Wikipedia, we see that `pdf` has a couple of mime-types. In return looking at our extraction and the +trace-model, we see both at `data.raw.mimeType`, with a further explanation in the :ref:`Hansken trace model` that +the `raw` portion of the property is the __data type__ of the datastream. + +If we don't know which datastream has the `mimeType` property beforehand, we could use the broad-scoped `$data.` matcher +to look at every datastream. + +So our matcher becomes: + +```text + file.extension=pdf OR + ( + $data.mimeType=application\\/pdf OR + $data.mimeType=application\\/x-pdf + ) +``` + +##### Match on data size + +Some pdf files can be huge, meaning that parsing them will need a lot of resources. Could we add a data size check to +the matcher? According to the :ref:`Hansken trace model` `data` has a property `size` (similar to `mimeType`) that we +could use for this. + +.. note:: This is also a good way to check if a file is empty or not. + +Let's say our cutoff limit is 1 MB, meaning our matcher becomes: + +```text + 0 < $data.size < 1000000 AND + ( + file.extension=pdf OR + ( + $data.mimeType=application\\/pdf OR + $data.mimeType=application\\/x-pdf + ) + ) +``` + +##### Match if 'property is set' + +It is not uncommon to have some overlap between tools/plugins. For example: + +- PdfPlugin: a plugin that only supports pdf documents +- DocumentPlugin: this plugin supports a lot of document types, including `pdf`. + +So how would we prevent our plugin from processing a trace that has already been processed by the `DocumentPlugin`? + +The easiest solution would be to check if a certain property has already been set. Meaning, that if both plugins set +the `foo.bar` property, we check if said property has already been set. + +So we __only__ process the trace if `foo.bar` is __empty__, meaning our matcher becomes: + +```text + foo.bar!=* AND + 0 < $data.size < 1000000 AND + ( + file.extension=pdf OR + ( + $data.mimeType=application\\/pdf OR + $data.mimeType=application\\/x-pdf + ) + ) +``` + +##### Match on excluding a certain path + +It is also not uncommon to exclude certain paths from your plugin. These paths might contain invalid or encrypted files, +for example. + +So let's say we want to exclude all files under in the `/tmp/virus` path. How do we go about it? + +Again, we check our extraction/:ref:`Hansken trace model`, and we see that `file.path` looks promising. + +So excluding `/tmp/virus` would look something like: + +```text + -file.path=/tmp/virus* AND + foo.bar!=* AND + 0 < $data.size < 1000000 AND + ( + file.extension=pdf OR + ( + $data.mimeType=application\\/pdf OR + $data.mimeType=application\\/x-pdf + ) + ) +``` + +.. _hql datastreams: + +##### Match on specific datastream type, an anti-pattern + +.. warning:: Matching on specific datastream types is an anti-pattern! It is not recommended to match on a datastream + type. Instead one should match on other datastream properties, such as `fileType`, `mimeType` or `mimeClass`. + The reason for this is explained in the paragraph below. + +Using a matcher that is too loose or too tight can yield unexpected results. Usually one will match on properties +of a datastream like `fileType`, `mimeType` or `mimeClass` as these are reliable properties that have been added by +Hansken tools. Matching on a specific datastream says nothing about the type of file. For example a PDF file may be +available in a `raw` as well as in a `decrypted` datastream. By matching on the datastream type one may exclude traces +that were not intended to be excluded. +Contrarily, note that matching on a datastream type may include *more* traces than you expected as well. For example, +someone may think "Plugin A puts data on the `plain` datastream, so I'll match on the `plain` datastream with Plugin B", +forgetting that `plain` may be used by other tools as well. In other words, there may be traces with that datastream +type that you did not know of, potentially crashing your plugin. See :ref:`datastreams` for more information. + +Now that you know why it is an anti-pattern, lets explain how it would be done (for those edge cases where it's needed): +Lets say we want our `PdfPlugin` to __ONLY__ process `raw` datastreams. +The best way to do this would be to match +on `$data.type:raw`. Note that `$data.type` matches against the type of the current datastream, so in this case it +matches only when the current datastream is of type `raw`. + +An __incorrect__ way to do it would be to replace `$data.` matcher(s) with `data.raw.`. This means the matcher +will match whenever a trace has this datastream type, even if the current datastream type is different. +Remember that the `process` method of an extraction plugin is always called once for each datastream on each trace. +For example, lets say a trace has two datastreams, `raw` and `text`. The matcher would match for both the datastreams +because the trace has a `raw` datastream (even though the current datastream type may be `text`). This results in the +`process` method being called twice (for `raw` and for `text`), which may lead to other bugs if the developer doesn't +know this. For example, the second time the plugin may be trying to overwrite data on a trace which is prohibited. + +So, using `$data.type`, our matcher would look like: + +```text + $data.type:raw AND + -file.path=/tmp/virus* AND + foo.bar!=* AND + 0 < $data.size < 1000000 AND + ( + file.extension=pdf OR + ( + $data.mimeType=application\\/pdf OR + $data.mimeType=application\\/x-pdf + ) + ) +``` + +## How precise should a matcher be? + +In practice, only you as the plugin dev can answer this question. + +Know that from the point of view of Hansken, we only care that the plugin: + +- __Should not crash__: If a matcher does not compile, then your plugin will not be available in Hansken. Tip: be sure + to test your plugin with the :ref:`test framework `. +- __Should not be slow__: Matching is designed to be extremely fast, but of course, if you make it too complex it can + take longer than we want. In the example above, we calculated that 1 second extra for 1 million traces is 11 days of + extra CPU time. Unlike processing, matching is done for __every trace__, in every extraction iteration, so be careful! +- __Should match on the bare minimum__: Don't go too far by matching 50 different criteria before allowing a trace to be + processed. Note that a lot of (if not all) of these criteria depend on properties set by other tools, and you don't + really have any control on how these tools work. diff --git a/0.7.0/_sources/dev/concepts/isolation.md.txt b/0.7.0/_sources/dev/concepts/isolation.md.txt new file mode 100644 index 0000000..8932753 --- /dev/null +++ b/0.7.0/_sources/dev/concepts/isolation.md.txt @@ -0,0 +1,28 @@ +# Plugin isolation + +Extraction plugins allows arbitrary code to be executed during a Hansken extraction. This code is executed inside the +Hansken cluster. Extraction plugins are subjected +to [Hanskens design principles](https://www.sciencedirect.com/science/article/pii/S1742287615000857?via%3Dihub) +such as security, privacy and transparency. To ensure that plugins are compliant to these principles, each plugin will +be executed in isolation. This page describes the isolation measures that are in place. + +## User isolation + +The following user restrictions are implied on the plugin: + +* The plugin can not run as `root`. +* Instead, the plugin will run as user `1000` +* and with group `2000` +* and with fsgroup `3000` + +## System calls + +Plugins are only allowed to call a limited set of (Linux) system calls. This ensures that a plugin can be executed in a +secure manner within the Hansken platform. + +Hansken uses Kubernetes to run extraction plugins. The Kubernetes `RuntimeDefault` secure computing mode (`seccomp`) is +enabled to provide a sane default of available system calls. + +## Network isolation + +Plugins are not allowed to communicate over the network. diff --git a/0.7.0/_sources/dev/concepts/kubernetes_autoscaling.md.txt b/0.7.0/_sources/dev/concepts/kubernetes_autoscaling.md.txt new file mode 100644 index 0000000..6ffa7b4 --- /dev/null +++ b/0.7.0/_sources/dev/concepts/kubernetes_autoscaling.md.txt @@ -0,0 +1,27 @@ +# Kubernetes, Autoscaling, Resourcemanagement + +Extraction Plugins can be run in a Kubernetes cluster. This can be the same cluster Hansken run on, or another external +cluster. For each plugin, a *pod* is created by Hansken. Each plugin will have 12 threads by default to process traces +separately within one pod. + +## Autoscaling + +Hansken will create a Horizontal Pod Autoscaler (*HPA*) for each pod. HPA's manage the number of replica's of a pod +based on metrics. The Extraction Plugins SDK provides two metrics to be set in the PluginInfo: + +* Observed CPU utilization +* Observed memory usage + +For more info on how to set these metrics follow these links: + +* [Specifying system resources (Java)](../java/snippets.md#Specifying system resources) +* [Specifying system resources (Python)](../python/snippets.md#Specifying system resources) + +If a pod reaches the CPU or memory usage provided with the PluginInfo, the HPA will increase the number of replicas for +that plugin. Scaling down is done automatically. The maximum number of replicas per pod is specified within Hansken +properties and can be adapted by an operator (needs restart). + +## Finding the right settings + +This depends on the kubernetes cluster settings, the nodes it runs on, and of course the plugin itself. Monitor the +number of replica's and resource metrics while an extraction is running and adapt accordingly. diff --git a/0.7.0/_sources/dev/concepts/plugin_naming_convention.md.txt b/0.7.0/_sources/dev/concepts/plugin_naming_convention.md.txt new file mode 100644 index 0000000..cd0e9ad --- /dev/null +++ b/0.7.0/_sources/dev/concepts/plugin_naming_convention.md.txt @@ -0,0 +1,44 @@ +# Plugin naming convention + +## Plugin identifier + +Each extraction plugin has a unique identifier. The identifier consists of three fields. These three fields combined +form the plugin name. + +The three fields of a plugin identifier are: *domain*, *category*, and *name*. The fields are described in more detail +below. + +**domain** +The domain name of the organisation where the plugin is created. If an organisation has multiple domain names, the +shortest name is preferred over the longer domain names. Examples: `nfi.nl`, `politie.nl`, `fiod.nl`, `hansken.org`. + +**category** +A type of action that the plugin performs. The category is a free text field, but the following table gives some +recommendations. + +| Category | Description | +|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| ``extract`` | The plugin parses a clear data structure | +| ``carve`` | The plugin parses data fragments to reassemble traces in the absence of filesystem metadata | +| ``classify`` | The plugin categorizes a plugin based on its content, e.g. detecting money on traces of type `picture` | +| ``digest`` | The plugin digests data to compute a hash | +| ``ocr`` | The plugin applies ocr (optical character recognition) to read text on `pictures` or scanned documents | +| ``match`` | The plugin matches a trace against a database, and reports whether there was hit or miss, e.g. matching a trace to a well known files database | + +**name** +The name of the plugin, or in the classic sense, a description detailing what the plugin processes. Note that the name +can contain (forward) slashes. + +## Examples + +The following table shows a list of plugin identifiers. The last column of the table shows the derived full plugin name. +The derived full plugin name will be shown in Hansken. + +| Domain | Category | Name | Derived plugin name | Explanation | +|-----------------|--------------|-----------------|------------------------------------|-------------------------------------------------------------------------------------------------------------| +| ``hansken.org`` | ``extract`` | ``archive`` | ``hansken.org/extract/archive`` | A plugin created by the Hansken development team that extracts traces from an arbitrary ``archive`` format | +| ``nfi.nl`` | ``extract`` | ``archive/zip`` | ``nfi.nl/extract/archive/zip`` | A plugin created by an NFI team that extracts traces from a specific ``archive`` format: ``zip`` | +| ``politie.nl`` | ``extract`` | ``archive/zip`` | ``politie.nl/extract/archive/zip`` | The same as the previous example, but now the plugin is created by a different organisation: ``politie.nl`` | +| ``hansken.org`` | ``carve`` | ``archive/zip`` | ``hansken.org/carve/archive/zip`` | A plugin that carves data to detect a specific ``archive`` format: ``zip`` | +| ``hansken.org`` | ``digest`` | ``sha256`` | ``hansken.org/digest/sha256`` | A plugin that digests data to compute a ``sha256`` hash | +| ``hansken.org`` | ``ocr`` | ``tesseract`` | ``hansken.org/ocr/tesseract`` | A plugin that performs ocr using ``tesseract`` | diff --git a/0.7.0/_sources/dev/concepts/plugin_types.md.txt b/0.7.0/_sources/dev/concepts/plugin_types.md.txt new file mode 100644 index 0000000..82d5d4a --- /dev/null +++ b/0.7.0/_sources/dev/concepts/plugin_types.md.txt @@ -0,0 +1,59 @@ +# Extraction plugin types + +Currently three types of Hansken Extraction Plugins are supported: + +* Standard Extraction Plugins +* Meta Extraction Plugins +* Deferred Extraction Plugins + +## Standard Extraction Plugins + +Standard Extraction Plugins provide enough functionality for most cases. This includes: + +* Processing traces +* Creating child-traces +* Reading trace properties +* Adding trace properties +* Reading data +* Writing data +* Writing [data transformations](data_transformations.md) + +## Meta Extraction Plugins + +Meta Extraction Plugins can only process and produce trace properties without the need (or possibility) for processing +actual trace data. This includes: + +* Processing traces +* Creating child-traces +* Reading trace properties +* Adding trace properties + +.. _deferred extraction plugins: + +## Deferred Extraction Plugins + +These plugins have two additional features that are not included with Standard Extraction Plugins: + +* a developer can choose to *defer* their execution +* information about other traces can be obtained while processing the current extraction trace. Code examples can be + found here: :ref:`Java `, :ref:`Python `. + +*Deferring execution* +A single Hansken image extraction consists of multiple *iterations*. Within every *iteration*, every Hansken tool and +plugin is executed on matching traces, which produces new traces or modifies existing traces. If a tool can be executed +another time because of these additions or modifications, another iteration is started. + +A regular plugin is executed in the same iteration a trace is matched. Deferred plugins are executed in a different +iteration; they are always deferred for at least one iteration. This is very useful when searching for traces, because +you are certain the deferred plugin is executed after all other tools performed their modifications. + +Sometimes, executing your plugin in the next iteration is not enough; it needs to be executed in a different iteration. +This is why the SDK allows setting a *deferredIterations* parameter in the plugin info. After the plugin matches with a +trace, it will be executed after *deferredIterations*. The execution can currently be deferred by a maximum of **20** +iterations and the default is **1**. + +*Searching for traces* +This type of plugin can perform a search to look for saved traces in the current **project**. This search is performed +using a provided **HQL** query. A maximum of **50** traces is returned for a given search request. + +.. warning:: Please note that HQL-Lite specific syntax such as the `Data` or `DataType` matchers is **NOT** supported. diff --git a/0.7.0/_sources/dev/concepts/test_framework.md.txt b/0.7.0/_sources/dev/concepts/test_framework.md.txt new file mode 100644 index 0000000..a937c31 --- /dev/null +++ b/0.7.0/_sources/dev/concepts/test_framework.md.txt @@ -0,0 +1,276 @@ +# Test framework + +.. _test_framework: + +The SDK provides the _FLITS Test Framework_ for integration testing. This allows us to test/validate the plugin input +and output without having a running Hansken instance. + +To use the test framework, three components are required: + +1. A running server instance of an extraction plugin. See section :ref:`How to test your plugin `. +2. Input test data +3. Results (expected output) + +## Creating test data + +The test data is independent of which programming language is used for the plugin (Java or Python). This section +describes the setup of the test data, while the sections thereafter will link to the language specific documentation. + +.. _basictestdata: + +### Basic test data directory structure + +Example test data directory structure with an `inputs` and `results` directory: + +```text +tests/ +├── inputs +│  ├── example1.raw +│  ├── example1.text +│  ├── example1.trace +│  ├── example2.raw +│  └── example2.trace +└── results +   ├── example1.raw.PluginName.trace +   ├── example1.text.PluginName.trace +   └── example2.raw.PluginName.trace + ``` + +The `inputs` folder contains all traces that will be processed during the test. These 'input traces' are defined in +files with the '.trace' extension, using JSON. This JSON structure is explained in section +:ref:`Trace format`. Each trace may have various :ref:`data-streams`. The data for each trace +is put into separate files for each data-stream. The data-stream files need to have the same name as their corresponding +trace file but differ in extension. They can have any extension, for example 'raw', 'text' or 'jpeg'. __Note that one +input trace will always have one '.trace' file, and can have none, one or many data files.__ Also note that if the +plugin doesn't match on any of the input files and there are no result files yet, the test will succeed. + +.. note:: The test-framework uses the extension of the input test file(s) _(other than __.trace__)_ as type of the + current data-stream. + +The expected results (which are also traces) are stored in a separate `results` folder next to the `inputs` folder. The +file names in the `results` folder correspond to the file names in the `inputs` folder. Note that the name of the plugin +is added between the file basename and the file extension. This can be useful if one maintains a single test input and +output test datasets for multiple extraction plugins. + +.. note:: It is possible to let the test framework regenerate the results files automatically. See the + :ref:`Java` and :ref:`Python` sections on testing on how to do this. If no files are + being generated, check if the plugin matcher is actually matching the input files. + +The test runner will invoke the extraction plugin for each input trace. The test runner collects the plugin output and +compares it against the trace defined in the `results` folder. If there is a mismatch, the test runner will fail with an +exit code 1. If all tests pass the test runner will finish with exit code 0. + +Given the files in the example above, the test runner will invoke the extraction plugin three times: + +| Input | Result | +|--------------------------------------------------------------|------------------------------------------------| +| `example1.trace` with data `stream example1.raw` | `example1.raw.PluginName.trace` | +| `example1.trace` with data `stream example1.text` | `example1.text.PluginName.trace` | +| `example2.trace` with data `stream example2.text` | `example2.raw.PluginName.trace` | + +### Test data structure for deferred extraction plugins + +:ref:`Deferred extration plugins ` have the unique ability to search traces with a query. +The `input` test data should be extended to contain the results of searches done by deferred extraction plugins. These +_search traces_ are stored in separate folders that follow the naming format '{deferred trace name}/searchtraces/'. Below +is an example test data directory structure for a deferred extraction plugin that searches for +a `deferredExampleSearch.trace`: + +```text +tests/ +├── inputs +│  ├── deferredExample.trace +│  ├── deferredExample.raw +│  ├── deferredExample/ +│  │  ├── searchtraces/ +│  │  │  ├── deferredExampleSearch.trace +└── results +   └── deferredExample.raw.DeferredPluginName.trace +``` + +.. warning:: The plugin will try to match on all traces in the input folder, including traces used for search results ( + of deferred extraction plugins). This means that it is impossible to search on traces that match the same deferred + extraction plugin, as it would create an infinite loop. + +Given the files in the example above, the test runner will invoke the extraction plugin one time: + +| Input | Result | +|----------------------------------------------------------------|------------------------------------------------| +| `deferredExample.trace` with data stream `deferredExample.raw` | `deferredExample.raw.DeferredPluginName.trace` | + +.. warning:: The search query should be written in HQL, as that is how Hansken will interpret it. However, the test + framework interprets the query using its HQL-lite interpreter. Therefore, not all queries will be supported. + +.. _traceformat: + +### Trace format + +Input and result traces both stored in a JSON structure. There is however a slight difference between the two: The +result trace may store additional values that are purely there for testing purposes. The input format will first be +discussed, followed by the result format. + +#### Input trace JSON format + +Input traces start with a `trace` key, which contains a mapping of properties. The property names are split in a +dictionary structure. The example below shows a serialized trace with six properties: `data.raw.mimeClass` and the five +data types that are currently supported by the test-framework. + +The `data` key defines the :ref:`data-streams ` of the trace. When adding a data-stream make sure you also +add the corresponding input data file, as described :ref:`above`. + +```json +{ + "trace": { + "data": { + "raw": { + "mimeClass": "text" + } + }, + "supported data types": { + "Boolean": true, + "Integer": 1, + "Double": 0.1, + "String": "a string", + "StringList": [ + "a", + "b", + "c", + "d" + ] + } + } +} +``` + +.. warning:: The extraction plugin SDK and the test framework have no knowledge of the + :ref:`trace model `. This means that when properties are used that don't + comply with the trace model, this will not cause the test to fail, but it will fail when running your plugin in Hansken. + +#### Result trace JSON format + +The result traces have the same format as the input traces, namely a `trace` key which contains the full input trace +with all its properties. However, the result traces may have two additional keys `children` and `data` (which are +explained in-depth below). These are added for testing purposes. If the plugin adds :ref:`child traces ` +or writes [data transformations](data_transformations.md) to Hansken, this would normally not reflect on the JSON of the +trace. However, the test framework adds these to the result JSON structure to be able to test them. + +Consequently, result traces are stored in a JSON structure that may consist of up to three parts, namely the always +present `trace` and the occasional `children` and `data`: + +* `trace`: The key `trace` contains a mapping of its properties, in exactly the same way as is done for input traces. +* `children`: :ref:`Child traces ` that have been created by the plugin during the test are stored under a + reserved field `children`, which is a list of traces. The example trace below contains a child trace with a property + `name`. +* `data`: [Data transformations](data_transformations.md) that have been created by the plugin during the test are + stored under a reserved field `data`. For each data-stream type there is a `descriptor` field describing the data + transformation in a JSON format. The example trace has a ranged data transformation for the raw data-stream. Note that + this `data` is entirely different from the `data` key that may be present _inside_ the `trace`! + +```json +{ + "trace": { + "data": { + "raw": { + "mimeClass": "text" + } + }, + "supported data types": { + "Boolean": true, + "Integer": 1, + "Double": 0.1, + "String": "a string", + "List": [ + "a", + "b", + "c", + "d" + ] + } + }, + "children": [ + { + "trace": { + "name": "child trace 1" + } + } + ], + "data": { + "raw": { + "descriptor": "[{\"ranges\":[{\"length\":79,\"offset\":0}]}]" + } + } +} +``` + +### Testing exceptions + +Some scenarios may throw exceptions and this can be part of your tests too. For example, an input file that has the +wrong format can be part of your integration tests. When an exception occurs during the test, it will be written to the +result file. This can be deliberately used to test exceptions. However, it is often impractical to match against a full +exception. For example, the row numbers in the exception are very much prone to change due to circumstances irrelevant +to the case being tested. Therefore, the testframework provides some options to match only on those parts of result +files that are relevant to the test. + +The following sections will explain these partial result matchers using the following example exception: + +```json +{ + "class": "org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginException", + "message": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris faucibus varius sodales." +} +``` + +#### Leaving out the message + +It is possible to leave of the message of the exception, which will still result in a valid result: + +```json +{ + "class": "org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginException" +} +``` + +#### The `startsWith` partial result matcher + +The `startsWith` partial result matcher requires a string as a parameter. The result will be valid if the actual result +starts with this string. + +```json +{ + "class": "org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginException", + "message.startsWith": "Lorem ipsum dolor sit amet, " +} +``` + +#### The `containsInOrder` partial result matcher + +The `containsInOrder` partial result matcher requires a list of strings as a parameter. The result will be valid if +every string in the list can be found in that same order in the actual result. + +```json +{ + "class": "org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginException", + "message.containsInOrder": [ + "Lorem ipsum dolor sit amet,", + "consectetur adipiscing elit.", + "Mauris faucibus varius sodales." + ] +} +``` + +.. _howtotestyourplugin: + +## How to test your plugin + +Running an integration test for an extraction plugin depends on the language in which the extraction plugin is built. + +### Java + +The Test Framework itself is built in Java. When building extraction plugins with Java, it can be incorporated in your +unit tests, as shown in [Using the Test Framework in Java](../java/testing.md). + +### Python + +The Python SDK also uses the Java based Test Framework. This is done by providing a wrapper to make calls to an included +Test Framework 'jar' file. See [Advanced use of the Test Framework in Python](../python/testing.md) for documentation +and examples on how to use FLITS for testing your Python plugin. diff --git a/0.7.0/_sources/dev/concepts/traces.md.txt b/0.7.0/_sources/dev/concepts/traces.md.txt new file mode 100644 index 0000000..de31583 --- /dev/null +++ b/0.7.0/_sources/dev/concepts/traces.md.txt @@ -0,0 +1,213 @@ +# Traces & Trace model + +.. _traces: + +## Traces + +Traces are structured data objects produced by tools/plugins during an extraction. A trace represents a piece of +information found in an evidence file. + +The following figure shows the main elements of a trace. Each element is described in more detail in the following +paragraphs. + +![trace.svg](trace.svg) + +.. The following diagram is used to produce the trace.png file + + @startuml + hide empty methods + + Interface Trace << (T,#FF7700 >> { + {static} String id + {static} String name + {static} String[] path + .. + Map properties + .. + List + List children + } + + Interface DataStream << (D,orchid>> { + {static} String type + {static} String length + {static} byte[] data + .. + String fileType + Properties properties + } + + Trace *-- Trace : a trace could have child traces + Trace *-- DataStream : a trace could have data streams + + @enduml + +### Types and Properties + +A trace has properties that describe the information of it by means of a property value. Trace properties are grouped by +a trace type. A trace can have multiple types. + +All types and properties that can be set are defined in the :ref:`Hansken trace model`. + +An example of a type is `document`, which could have the properties `application` and `createdOn`. The trace will have a +type `document`, and can the following properties with values: + + document.application: Libre Office + document.createdOn: 2021-09-18 20:00:00 + +### Intrinsic properties + +A trace has several *intrinsic* properties. These are properties that are not related to a trace type. The intrinsic +properties available to extraction plugins are: + +* `id`: a unique identifier of the trace, generated when a trace is created +* `name`: a name given to a trace when it is created +* `path`: a logical path of the trace, of which the elements are the names of traces from the root trace until this + trace + +.. _datastreams: + +### Data streams + +Typically, a trace represents a piece of data found in an evidence file. This data is part of the trace and available as +a data stream. A trace can have multiple data streams. Each data stream has a type. Data streams can also have +properties that apply to the data stream itself. The data stream properties are modeled as properties of the trace, in +the following pattern: +`data..propertyname` (where `` is substituted by the actual type of the data stream). + +The set of data stream types and data stream properties is fixed. All allowed types and properties are defined in the +:ref:`Hansken trace model` (see `data`). + +An important data stream property is the `fileType` property. This property contains a textual description of the +*detected* file type for the data stream. An example of a `fileType` is 'Adobe Pdf'. The `fileType` is a good candidate +to use in a extraction plugin 'matcher'. This `fileType` is detected by Hansken using file type heuristics, which are +primarily based on the data stream bytes itself, and secondarily on other metadata such as a file extension. +(N.B. The `fileType` is detected in Hansken by the extraction tool `Firefli`.) For more information on how datastream +properties can be used for matching, see :ref:`here`. + +Note that not all traces have data streams. In these cases it is a trace of meta-data derived from another trace. + +Usually, each trace with data has a data stream of type `raw`. This data stream contains the bytes of the traces as they +were found when the trace was created. In some occasions, the `raw` data can be represented in a different form before it +can be processed further, for example if the data can be decoded or decrypted. Hansken tools and plugins can decode +the `raw` data stream to a standard UTF-8 data stream, or can decrypt the data if a decryption key is present. Hansken +tools and extraction plugins can store the new data at the new trace in a new data stream. This new data stream has a +different type than the `raw` type. + +Examples in code can be found here: + +* Adding a Datastream :ref:`Java ` +* Adding a Datastream :ref:`Python ` + +.. TODO: Add a link to matcher in the paragraph above + +.. _child traces: + +### Child traces + +A trace can have child traces. For example, a trace of type `archive` can have children, where each child is a trace +that represents an entry in the archive. + +With an extraction plugin it is possible to create child traces for a trace that is being processed. New properties, +data streams, and other child traces can be set on the new child traces. When a child trace is created, the plugin +should provide a `name` for the child trace. The `id` of the child trace is generated, in the following +form: `parenttraceid-childnumber`. For example, if the parent has an id `0-0-0-0-0:0-9`, the first child gets the +id `0-0-0-0-0:0-9-1`, the second child gets the id `0-0-0-0-0:0-9-2`, and so on. + +Note that a trace does not have (direct) access to its parent trace. + +### Trace property types + +The SDK supports the following property types for traces: + +| | Java | Python | +|-------------|-----------------------|-----------------------| +| binary | `byte[]` | `bytes` / `bytearray` | +| boolean | `boolean` | `bool` | +| integer | `int` / `long` | `int` | +| real | `float` / `double` | `float` | +| string | `String` | `str` | +| date & time | `Date` | `datetime` | +| list | `List` | `list` / `tuple` | +| mapping | `Map` | `dict` | +| location | `LatLong` | `GeographicLocation` | +| vector | `Vector` | `Vector` | +| tracelet | *see Tracelets below* | *see Tracelets below* | + +Both location and vector types are available from the SDK, Java package `org.hansken.plugin.extraction.api` or Python module `hansken.util`. + +.. _vector: + +#### Vector + +A vector is a data type that can be used to store points in n-dimensional space as an array of floating point values. +Once indexed, the vectors can then be used in a gui or other client to search for traces that have a nearby vectors. +For example, it is possible to use a neural network that provides embeddings of human faces as vectors. Once indexed, +the vectors can then be used to find pictures with similar faces. To do this, the search rest api can be used to +sort by the euclidean- or manhattan distance, or cosine similarity to a given vector. + +.. _tracelets: + +#### Tracelets + +A Tracelet is a bundle of property values that belong to a single type. It is a property on a trace that can have +multiple properties itself, making it a list of key/value pairs. The API doesn't specify the cardinality, but the +implementation is limited to cardinality 'Few'. In Hansken these are called FVT's (Few Valued Types). + +.. note:: MVT's (Many Valued Types) are currently not supported in the SDK and will be added in a future release. + +An example of a tracelet is the `prediction` property, which describes a category or class a trace belongs to. It is +possible for a trace to have multiple predictions. Therefore `prediction` is a tracelet. Other examples of +tracelets are `identity` and `collection`. + +Examples in code can be found here: + +* Adding tracelets in :ref:`Java ` +* Adding tracelets in :ref:`Python ` + +.. _Hansken trace model: + +## Hansken trace model + +All traces in Hansken are based on a specific version of the trace model, and must comply to that version of the trace +model. This is a nested data structure composed of origins, categories, types and properties. + +All non-inrinsic trace properties are optional and are grouped by **type**. These types are defined under the trace +model section 'categories'. Every **category** has a list of allowed types. When a trace is identified as being a +document, it will get this set of predefined document properties. Trace types can have different **origins**. The +possible origins are defined in the trace model section 'origins'. An example of this is the processed types that are +always generated by the system during an extraction. + +The details of the current trace model can be retrieved using the `/tracemodel` +REST call on the Gatekeeper endpoint of Hansken, or check the Hansken Documentation on the trace model. + +.. TODO: nice screenshot on how to find the trace model in Hansken? + +.. _Trace model and the extraction plugin SDK: + +### Trace model and the extraction plugin SDK + +.. warning:: The extraction plugin SDK has no knowledge of the trace model + +The Extraction Plugins SDK has no knowledge of the trace model at this time. It is however possible to create new traces +with plugins. If any newly created Traces don't comply to the model, Hansken will not accept them and mark the plugin +execution as failed. The Extraction Plugins SDK and the provided [Test Framework](test_framework.md) don't check this. +Please make sure to use the right naming when creating new Traces, as provided by the trace model. + +If an erroneous trace property is set, Hansken will show an error. The error can be found in the Hansken Expert UI +interface by double-clicking on the trace. Then the trace details screen will be opened and the error will be displayed +as follows: + +.. image:: toolrun_error.png + +This error describes that a property does not exist in the trace model. To get more information about the error, the +extraction log can be viewed. In the extraction log you have to search +for `java.lang.IllegalArgumentException: no such type` to find out which property is not supported by the trace model. + +In the example extraction log below, the property `this_property_does_not_exist` could not be found 681 times. + + Cumulative warnings, based on the message without numbers, uuids and trace objects. Only showing full message for first warning of this type. + Count | Key | Message + 681 | org.hansken.ep.shade.io.grpc.StatusRuntimeException | CANCELLED: Cancelled by client with StreamObserver.onError(); org.hansken.ep.shade.io.grpc.StatusRuntimeException: ABORTED: java.lang.IllegalArgumentException: no such type: this_property_does_not_exist + 7 | java.lang.IllegalStateException | call was cancelled + 1 | org.hansken.ep.shade.io.grpc.StatusRuntimeException | UNAVAILABLE: HTTP/2 error code: NO_ERROR Received Rst Stream diff --git a/0.7.0/_sources/dev/examples.md.txt b/0.7.0/_sources/dev/examples.md.txt new file mode 100644 index 0000000..5be2f27 --- /dev/null +++ b/0.7.0/_sources/dev/examples.md.txt @@ -0,0 +1,5 @@ +# Examples + +Extraction Plugin Examples in both Java and Python can be found in the +[Hansken Extraction Plugin Examples Repository](https://github.com/NetherlandsForensicInstitute/hansken-extraction-plugin-sdk-examples) +hosted on Github. diff --git a/0.7.0/_sources/dev/faq.md.txt b/0.7.0/_sources/dev/faq.md.txt new file mode 100644 index 0000000..ee5a5f1 --- /dev/null +++ b/0.7.0/_sources/dev/faq.md.txt @@ -0,0 +1,64 @@ +# Frequently Asked Questions + +.. _communityaccess: + +## How can I access Hansken developer community + +You will first need git access to the Hansken developer community. Here you can find started guides and examples. If you +have no access yet, you can get access by following the next steps: + +1. Sign up at [git.eminjenv.nl](https://git.eminjenv.nl/) +2. After you have created your account, you should request access to the " + Hansken Community" group . Do this by contacting your organisations Hansken business owner. If you don't know who to + contact as your business owner, please read the :doc:`../contact` page. + +## Why use Extraction Plugins? + +Extraction plugins enable you to run your own extraction code in Hansken. + +* Your plugin runs during extraction +* Extraction plugins are faster than Hansken.py +* Multiple programming languages are supported +* Scalability: Extraction plugins can be scaled flexibly on a kubernetes cluster + +## What programming languages are supported? + +The SDK contains an API and tools to write a Hansken extraction plugin in Java or Python. The Java API can be used to +develop extraction plugins in JVM-compatible languages, such as Scala and Kotlin. + +## Will you support language *foobar*? + +Probably not. It takes time and effort to create a proper SDK. If you think there is a good use case to support +language *foobar*, and there is gRPC support, feel free to contact us. We can discuss the options to add support for +Hansken extraction plugins with *foobar*. + +Under the hood, extraction plugins use gRPC to communicate with Hansken. In theory, all programming languages that have +a gRPC implementation can be used to write Hansken extraction plugins. + +## Can I reuse or modify the Extraction Plugins SDK? + +The SDK is distributed under the Apache 2.0 License, see the LICENSE file in the SDK for more details. + +## Can I use a plugin that someone else wrote? + +Yes, at your own risk. The Hansken Team does not take responsibility for code written by third parties in the form of +Extraction Plugins. + +## What are the legal implications of creating your own Extraction Plugin(s)? + +All Extraction Plugins not written by the Hansken Team are considered **third party** Extraction Plugins. Please refer +to [Can I use a plugin that someone else wrote?](faq.md#can-i-use-a-plugin-that-someone-else-wrote?) + +## How safe are Extraction Plugins? + +We are doing everything to make sure Extraction Plugins are as safe as possible, however note that the Extraction Plugin +SDK is still in beta. Use it at your own risk. For more information on security see [Isolation](concepts/isolation.md). + +## Can my Extraction Plugin be embedded into Hansken for performance reasons? + +Embedding an Extraction Plugin into Hansken requires access to the Hansken source code. If you have access to the source +code then please `../contact` us for assistance. Please note that embedded Extraction Plugins are **not officially +supported**. + +If you do not have access to the Hansken source code, then please contact your own Business Owner, and ask them to +contact the Hansken Team. diff --git a/0.7.0/_sources/dev/introduction.md.txt b/0.7.0/_sources/dev/introduction.md.txt new file mode 100644 index 0000000..7f99bec --- /dev/null +++ b/0.7.0/_sources/dev/introduction.md.txt @@ -0,0 +1,50 @@ +# Introduction + +The Extraction Plugin Software Development Kit can be used to develop a [Hansken](https://www.hansken.nl/an-introduction-to-hansken) +extraction plugin. + +Hansken is designed to give access to and insight in digital data and traces originating from seized and demanded +digital material. One aspect of the Hansken platform is the extraction engine (extraction framework). The extraction +engine contains digital forensics knowledge, which is used to find traces in digital material. With extraction plugins +case investigators can add new digital forensics knowledge to the extraction framework. In this way, Hansken is enabled +to understand new digital formats, and thus is able to find new types of traces in the seized and demanded material. + +Examples of digital forensics knowledge that can be added to Hansken with extraction plugins: + +* new file formats (e.g. a new crypto currency wallet) +* combine traces to find new information (e.g. use a windows registry entry required to read a file from disk) +* apply algorithms on traces (e.g. speech to text from audio files) + +The primary goals of this SDK are: + +* to make it as easy as possible to add new digital forensics knowledge to Hansken. +* be able to share digital forensics knowledge with other Hansken community members + +## Software Development Kit (SDK) + +In order to create extraction plugins, the Hansken project maintains an Extraction Plugin Software Development Kit +(SDK). + +This SDK contains the following elements: + +* [Java API and tooling](java), to be able to write an extraction plugin with the [Java](https://www.java.com/) + programming language +* [Python API and tooling](python), to be able to write an extraction plugin with [Python](https://www.python.org/) + programming language +* [Test framework](concepts/test_framework.md), to be able to test extraction plugins before they are used production +* Documentation for plugin developers to help understand the SDK and all its facets (this documentation) +* :doc:`examples` + +## Development steps of a plugin + +To create a plugin, the plugin developer could follow the following steps. Detailed information per step will be added +later to the documentation. + +1. Create a new empty plugin +2. Implement the plugin logic +3. Verify the plugin using the [test framework](concepts/test_framework.md), using reference data (test data) and + expected output +4. Test the plugin in Hansken +5. Use the plugin in an actual case +6. Share the plugin with the Hansken community, so other case investigations can benefit from the plugin as well ( + optional, but encouraged) diff --git a/0.7.0/_sources/dev/java.rst.txt b/0.7.0/_sources/dev/java.rst.txt new file mode 100644 index 0000000..c6f37e9 --- /dev/null +++ b/0.7.0/_sources/dev/java.rst.txt @@ -0,0 +1,14 @@ +Java +==== + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + java/api_changelog + java/prerequisites + java/packaging + java/snippets + java/testing + java/debugging + java/javadoc diff --git a/0.7.0/_sources/dev/java/api_changelog.md.txt b/0.7.0/_sources/dev/java/api_changelog.md.txt new file mode 100644 index 0000000..dcc49df --- /dev/null +++ b/0.7.0/_sources/dev/java/api_changelog.md.txt @@ -0,0 +1,217 @@ +# Java API Changelog + +This document summarizes all important API changes in the Extraction Plugin API. This document only shows changes that +are important to plugin developers. For a full list of changes per version, please refer to the general +:ref:`changelog `. + +.. If present, remove `..` before `## |version|` if you create a new entry after a previous release. + +## |version| + +* Escaping the `/` character in matchers is optional. + This simplifies and aims for better HQL and HQL-Lite compatability. + See for more information and examples the :ref:`HQL-Lite syntax documentation`. + + Examples: + + * `file.path:\\/Users\\/*\\/AppData` -> `file.path:/Users/*/AppData` + * `registryEntry.key:\\/Software\\/Dropbox\\/ks*\\/Client-p` -> `registryEntry.key:/Software/Dropbox/ks*/Client-p` + +* Hansken returns `file.path` properties as a `String` property, instead of a `List`. + Example: `trace.get("file.path")` now returns `"/dev/null"`, this was `["dev", "null"]`. + + +## 0.6.1 + +* The JAVA SDK is now distributed through maven central instead of the Hansken community. + +## 0.6.0 + +.. warning:: It is highly recommended to upgrade your plugin to this new version. + See the migration steps below. + +* Extraction plugin container images are now labeled with PluginInfo. This + allows Hansken to efficiently load extraction plugins. + +* By default, extraction plugin version is managed in the plugin's `pom.xml`. + The `.pluginVersion(..)` can be removed from the PluginInfo builder. + +* **Migration steps from earlier versions** -- for plugins that use the Java + extraction plugin SuperPOM: + + 1. Update the SDK version in your `pom.xml` + 2. If you come from a version prior to `0.4.0`, or if you use a plugin name + instead of a plugin id in your `pluginInfo()`, switch to the plugin id style + (read instructions for version `0.4.0`) + 3. Set your plugin version in your project's `pom.xml`, and remove the + following from your `PluginInfo.Builder`: + + ```java + .pluginVersion(...) + ``` + + 4. Update your build scripts to build your plugin (Docker) container image. + You should build your plugin container image with the following command: + + ```bash + mvn package docker:build` + ``` + + This will generate a plugin image: + + * The extraction plugin is added to your local image registry + (`docker images`), + * The image name is `extraction-plugin/PLUGINID`, e.g. + `extraction-plugin/nfi.nl/extract/chat/whatsapp`, + * The image is tagged with two tags: `latest`, and your plugin version. + + Nb. If Docker is not available in your environment, `podman` can be used + as an alternative. See :ref:`packaging ` for more + details. + +## 0.5.0 + +* Add new tracelet api `Trace.addTracelet(type, consumer)`. + It can be used like this: + + ```java + trace.addTracelet("prediction", tracelet -> tracelet + .set("type", "classification") + .set("label", "label") + .set("confidence", 0.8f) + .set("embedding", Vector.of(1,2,3)) + .set("modelName", "yolo") + .set("modelVersion", "2.0")); + ``` + +* Deprecate Trace.addTracelet(Trace) +* Support vector data type in trace properties. + +## 0.4.13 + +* When writing input search traces for tests, it is no longer required to explicitly set an `id` property. + These are automatically generated when executing tests. + +## 0.4.7 + +* A new convenience method `id(String, String, String)` is added to the PluginInfo builder. This removes some + boilerplate code when setting the pluginId. More details on the plugin naming conventions can be found at the + :doc:`../concepts/plugin_naming_convention` section. + + ```java + PluginInfo.builderFor(this) + .id("nfi.nl", "extract", "TestPlugin") // new style + .id(new PluginId("nfi.nl", "extract", "TestPlugin")) // old style, but also works + ... + ``` + +## 0.4.6 + +* It is now possible to specify maximum system resources in the `PluginInfo`. To run a plugin with 0.5 cpu (= 0.5 + vCPU/Core/hyperthread) and 1 gb memory, for example, the following configuration can be added to `PluginInfo`: + + ```java + PluginInfo.builderFor(this) + ... + .pluginResources(PluginResources.builder() + .maximumCpu(0.5f) + .maximumMemory(1000) + .build()) + .build(); + ``` + +## 0.4.0 + +* Extraction Plugins are now identified with a `PluginInfo.PluginId` containing a domain, category and name. The + method `PluginInfo.name(pluginName)` has been replaced by `PluginInfo.id(new PluginId(domain, category, name)`. More + details on the plugin naming conventions can be found at the :doc:`../concepts/plugin_naming_convention` section. + +* `PluginInfo.name()` is now deprecated (but will still work for backwards compatibility). + +* A new license field `PluginInfo.license` has also been added in this release. + +* The following example creates a PluginInfo for a plugin with the name `TestPlugin`, licensed under + the `Apache License 2.0` license: + + ```java + PluginInfo.builderFor(this) + .id(new PluginId("nfi.nl", "extract", "TestPlugin")) // id.domain: nfi.nl, id.category: extract, id.name: TestPlugin + // .name("TestPlugin") // no longer supported + .pluginVersion("0.4.1") + .author(Author.builder()...build()) + .description("A plugin for testing.") + .maturityLevel(MaturityLevel.PROOF_OF_CONCEPT) + .hqlMatcher("*") + .webpageUrl("https://www.hansken.org") + .license("Apache License 2.0") + .build(); + ``` + +## 0.3.0 + +* Extraction Plugins can now create new datastreams on a Trace through data transformations. Data transformations + describe how data can be obtained from a source. + + An example case is an extraction plugin that processes an archive file. The plugin creates a child trace per entry in + the archive file. Each child trace will have a datastream that is a transformation that marks the start and length of + the entry in the original archive data. By just describing the data instead of specifying the actual data, a lot of + space is saved. + + Although Hansken supports various transformations, the Extraction Plugins SDK for now only supports ranged data + transformations. Ranged data transformations define data as a list of ranges, each range with an offset and length in + a bytearray. + + The following example sets a new datastream with dataType `html` on a trace, by setting a ranged data transformation: + + ```java + trace.setData("html", RangedDataTransformation.builder().addRange(offset, length).build()); + ``` + + The following example creates a child trace and sets a new datastream with dataType `raw` on it, by setting a ranged + data transformation with two ranges: + + ```java + trace.newChild(format("lineNumber %d", lineNumber), child -> { + child.setData("raw", RangedDataTransformation.builder() + .addRange(10, 20) + .addRange(50, 30) + .build()); + }); + ``` + + More detailed documentation will follow in an upcoming SDK release. + +## 0.2.0 + +.. warning:: This is an API breaking change. Plugins created with an earlier version of the extraction plugin SDK are + not compatible with Hansken that uses `0.2.0` or later. + +* Introduced a new extraction plugin type `DeferredExtractioPlugin`. Deferred Extraction plugins can be run at a + different extraction stage. This type of plugin also allows accessing other traces using the searcher. + +* The class `ExtractionContext` has been renamed to `DataContext`. The new name `DataContext` represents the class + contents better. Plugins have to update matching import statements and the type in `ExtractionPlugin.process()` + implementation in the same way. This change has no functional side effects. + + Old: + + ```java + import org.hansken.plugin.extraction.api.ExtractionContext; + + @Override + + public void process(final Trace trace, final ExtractionContext context) throws IOException { + + } + ``` + + New: + + ```java + import org.hansken.plugin.extraction.api.DataContext; + + @Override + public void process(final Trace trace, final DataContext dataContext) throws IOException { + + } + ``` diff --git a/0.7.0/_sources/dev/java/debugging.md.txt b/0.7.0/_sources/dev/java/debugging.md.txt new file mode 100644 index 0000000..2674eb4 --- /dev/null +++ b/0.7.0/_sources/dev/java/debugging.md.txt @@ -0,0 +1,134 @@ +# How to debug an Extraction Plugin + +Debugging is the art of removing bugs — hopefully quickly. + +## Locally + +To debug a plugin locally, it is recommended to start the plugin via the IDE by running the integration test. This has +the advantage that breakpoints can easily be put in the code instead of printing log statements, for example. + +### Logging + +The logging of the extraction plugin is displayed in the console. + +## Locally with Docker + +Debugging an extraction plugin via docker is a bit trickier. Java has the advantage that remote debugging is already +baked in. + +Using Java Remote Debug with Docker containers requires 3 distinct steps: + +1. Build a Docker image +2. Run the Docker image with specific Java tool options +3. Setting breakpoints in your code + +### Build a Docker image + +If the Docker image is not built, run the following command to build the Docker image: + +```bash +mvn package docker:build +``` + +### Run the Docker image with specific Java tool options + +In Java, the remote debug functionality is not enabled by default. To enable the remote debug functionality, the +following environments variable must be set in the Docker container: + +```bash +JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" + ``` + +This environment variable allows the debugger to connect to the debuggee (application being debugged). To start the +Docker image with the `JAVA_TOOL_OPTIONS` environment variable, the following command can be used: + +```bash +docker run -p 5005:5005 -e JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" your_extraction_plugin_name +``` + +The next step is to attach the debugger to the debuggee. +For Intellij, the instructions are clearly described on the following +page: [Tutorial: Remote debug](https://www.jetbrains.com/help/idea/tutorial-remote-debug.html#49be7f04) + +### Setting breakpoints in the code + +The last step is to add breakpoints in the code. + +### Logging in Docker + +The logging of the extraction plugin is displayed in the console after running the `docker run` command. In addition, +the logging is also displayed in the IntelliJ console while debugging. + +## Kubernetes + +In kubernetes it is currently _not_ possible to debug via Java Remote Debug because: + +* no debug ports are published; +* the container was not started with the environment variable `JAVA_TOOL_OPTIONS` so debugging is not enabled. + +### Logging in Kubernetes + +If there is authorization to the kubernetes cluster, the logging can be viewed with the following command: + +```bash +kubectl logs -f hansken-extraction-plugins/your_extraction_plugin_pod +``` + +## Debug HQL + +An HQL query can be debugged by overriding the `isVerboseLoggingEnabled()` method of the `ExtractionPluginFlits` class. +The example below shows an example of an embedded FLITS test with verbose logging enabled. + +```java +public class TestPluginFlitsIT extends EmbeddedExtractionPluginFlits { + + @Override + public Path testPath() { + return srcPath("integration/inputs/plugin"); + } + + @Override + public Path resultPath() { + return srcPath("integration/results/embedded/plugin"); + } + + @Override + protected ExtractionPlugin pluginToTest() { + return new TestPlugin(); + } + + @Override + public boolean regenerate() { + return true; + } + + @Override + protected boolean isVerboseLoggingEnabled() { + return true; + } +} +``` + +The following output will then be displayed in the console: + +```text +HQL match found for: +$data.type=jpg +With trace: +dataType=jpg +types={file, data} +properties={data.raw.mimeType=image/jpg, path=/test-input-trace, file.name=image.jpg, name=test-input-trace, id=0} +``` + +If the HQL query contains an error, it will be shown in the generated test results. An example of an invalid query +is ``$data.mimeType=image/jpg`` (slash not escaped). This query will produce an error like the one shown below. + +```json +{ + "class": "org.hansken.plugin.extraction.hql_lite.lang.ParseException", + "message": "HqlLiteHumanQueryParser: line 1:20 token recognition error at: '/jpg'" +} +``` + +.. note:: The error is only shown in the generated trace, so to find out the `ParseException` override + the `regenerate()` method from `Flits` and then let this method return `true`. diff --git a/0.7.0/_sources/dev/java/javadoc.md.txt b/0.7.0/_sources/dev/java/javadoc.md.txt new file mode 100644 index 0000000..fdca19d --- /dev/null +++ b/0.7.0/_sources/dev/java/javadoc.md.txt @@ -0,0 +1,3 @@ +# Javadoc + +Visit the `Javadoc of the Extraction Plugins SDK API <../../_static/javadoc/index.html>`_. diff --git a/0.7.0/_sources/dev/java/packaging.md.txt b/0.7.0/_sources/dev/java/packaging.md.txt new file mode 100644 index 0000000..0a51a06 --- /dev/null +++ b/0.7.0/_sources/dev/java/packaging.md.txt @@ -0,0 +1,42 @@ +# Packaging + +Extraction plugins are packaged as OCI images (also known as Docker images). +The OCI images are labeled with the PluginInfo. +To automate packaging of a Java plugin and labeling the OCI image, the Extraction Plugin SuperPom has been configured to automate this for you. + +If your project uses the Extraction Plugin SuperPom (see [Prerequisites](prerequisites.md)), Packaging an extraction plugin is handled by Maven. +To package your plugin into a container image, the following command can be used: + +```bash +mvn package docker:build +``` + +This will generate a plugin image: + +* The extraction plugin is added to your local image registry +(`docker images`), +* The image name is `extraction-plugin/PLUGINID`, e.g. +`extraction-plugin/nfi.nl/extract/chat/whatsapp`, +* The image is labeled with two tags: `latest`, and your plugin version. + +It is possible to apply extra arguments to the docker command [as described here](http://dmp.fabric8.io/#docker:build). +For example, to specify a proxy, use the following command: + +```bash +mvn package docker:build -Ddocker.buildArg.https_proxy=https://proxy:8001 +``` + +Once your plugin is packaged, it can be published or 'uploaded' to Hansken. +See ":ref:`upload_plugin`" for instructions. + +.. _java_superpom_podman: + +Note: if your build environment does not have Docker available, you can use +[podman](https://podman.io/) as an alternative. Install podman on your machine +or build agent, and run the following commands _before_ invoking the +`mvn package docker:build` command: + +```bash +podman system service --time=0 unix:/run/user/$(id -u)/podman/podman.sock & +export DOCKER_HOST="unix:/run/user/$(id -u)/podman/podman.sock" +``` diff --git a/0.7.0/_sources/dev/java/prerequisites.md.txt b/0.7.0/_sources/dev/java/prerequisites.md.txt new file mode 100644 index 0000000..60e8136 --- /dev/null +++ b/0.7.0/_sources/dev/java/prerequisites.md.txt @@ -0,0 +1,41 @@ +# Prerequisites + +Required software: + +* Java 11 or higher +* Docker for [packaging](packaging.md) and publishing plugins + (or use a Docker alternative such as `podman`) +* Maven (recommended, build automation tool) + +Required dependencies: +* All required project dependencies to build extraction plugins are published on the public Maven Central, under `org.hansken.plugin.extraction:plugin-super-pom`. + For maven based extraction plugins, the following `pom.xml` snippet can be used as basis of a plugin: + ```xml + + + 4.0.0 + + + org.hansken.plugin.extraction + plugin-super-pom + SET_THE_SDK_VERSION_HERE + + + CHOOSE_YOUR_ARTIFACTID_HERE + SET_THE_PLUGIN_VERSION_HERE + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + SET_THE_PLUGIN_MAIN_CLASS_HERE + + + ``` diff --git a/0.7.0/_sources/dev/java/snippets.md.txt b/0.7.0/_sources/dev/java/snippets.md.txt new file mode 100644 index 0000000..6955067 --- /dev/null +++ b/0.7.0/_sources/dev/java/snippets.md.txt @@ -0,0 +1,246 @@ +# Java code snippets + +This page contains Java code snippets for common patterns that will be used when writing a plugin. + +## RandomAccessData as InputStream + +In Java, `InputStream` is a common type to pass data to another class or method. The SDK provides a simple utility to +use a `RandomAccessData` as `InputStream`. + +Add the following import to your code: + +```java +import org.hansken.plugin.extraction.core.data.RandomAccessDatas; +``` + +Next we can create an `InputStream` from the `RandomAccessData` as shown in the following snippet. Note that the +`InputStream` is created using a `try-with-resources`-statement. This ensures that the `InputStream` is correctly closed +when the `InputStream` is no longer required. + +```java +RandomAccessData traceData=...; + try(InputStream asInputStream=RandomAccessDatas.asInputStream(traceData)){ + // use the InputStream here + } +``` + +Notes: + +* the created `InputStream` is *not* thread-safe, +* the created `InputStream` changes state in the provided `RandomAccessData` + (e.g. when data is read, the position updated in both the `InputStream` *and* + the `RandomAccessData` instances), +* for more details on the implementation of the `InputStream`, refer to the `RandomAccessDataInputStream` JavaDoc. + + +.. _tracelets java: + +## Adding tracelets + +In the following Java example, a "classification" :ref:`tracelet` is added to a trace. The tracelet consists +of a list of four properties, namely "class", "confidence", "modelName" and "modelVersion". + +```java +trace.addTracelet("prediction", tracelet -> tracelet + .set("type", "classification") + .set("class", "telephone") + .set("label", "label") + .set("confidence", 0.8f) + .set("embedding", Vector.of(1,2,3)) + .set("modelName", "yolo") + .set("modelVersion", "2.0")); +``` +or +```java +trace.addTracelet(new Tracelet("prediction", List.of( + new TraceletProperty("prediction.type","classification"), + new TraceletProperty("prediction.class","telephone"), + new TraceletProperty("prediction.label","label"), + new TraceletProperty("prediction.confidence",0.8f))), + new TraceletProperty("prediction.embedding", Vector.of(1,2,3)), + new TraceletProperty("prediction.modelName","yolo"), + new TraceletProperty("prediction.modelVersion","2.0")); +``` + + +.. _datastreams java: + +## Adding data to a trace + +Traces can have data attached to them. See :ref:`datastreams` for more information. +The following two snippets demonstrate how to add data to a trace. + +It is currently not possible to verify that a specific data stream is already set or not. + +### Data Transformations + +The most efficient way to add data to a trace is using data transformations. +See :doc:`../concepts/data_transformations` for more details. + +The following example sets a new data stream with dataType `html` on a trace, by setting a ranged data transformation: + +```java +trace.setData("html", RangedDataTransformation.builder().addRange(offset, length).build()); +``` + +The following example creates a child trace and sets a new datastream with dataType `raw` on it, by setting a ranged +data transformation with two ranges: + +```java +trace.newChild(format("lineNumber %d", lineNumber), child -> { + child.setData("raw", RangedDataTransformation.builder() + .addRange(10, 20) + .addRange(50, 30) + .build()); +}); +``` + +### Blobs + +It is not always possible to create a transormation for the data that has to be +added to a trace. For example the data is a result of a computation, and not +a direct subset of another data stream.. + +The following examples show how to creates a new data stream of dataType `raw` on a trace. + +In case all data is stored in a `byte[]`, we can add the byte array to the data stream with: + +```java +final byte[] rawBytes = {.....}; +trace.setData("raw", writer -> writer.write(rawBytes)); +``` + +Alternatively, if the data is available in an `InputStream` the data can be added with: + +```java +final InputStream inputStream = ...; +trace.setData("raw", inputStream); +``` + +## Specifying system resources + +In the `PluginInfo` you can specify **maximum** system resource metrics for a plugin. These are used for scaling the +number of pods as described [here](../concepts/kubernetes_autoscaling.md#Autoscaling). To run a plugin with 0.5 cpu (= +0.5 vCPU/Core/hyperthread) and 1 gb memory, for example, the following configuration can be added to `PluginInfo`: + +```java +PluginInfo.builderFor(this) + ... + .pluginResources(PluginResources.builder() + .maximumCpu(0.5f) + .maximumMemory(1000) + .build()) + .build(); +``` + +.. _java_snippets_deferred: + +## Deferred Extraction Plugins + +Using a deferred plugin requires inheriting the `DeferredExtractionPlugin` base class. This allows access to +a ``TraceSearcher`` object in the process function to search for traces. + +```java +public class ExampleDeferred extends DeferredExtractionPlugin { + @Override + public PluginInfo pluginInfo(); + + @Override + public void process(final Trace trace, final ExtractionContext context, + final TraceSearcher searcher) { + final SearchResult result = searcher.search("file.extension=asc", 10); + } +} +``` + +The ``search`` method accepts a HQL query and a count, which represents the maximum number of traces to return. It may +be useful to specifically search for traces from the image being extracted. Add ``"image:" + trace.get("image")`` to +your query. The query of the provided example could be extended like this: +`"file.extension = asc AND image:" + trace.get("image")`. + +The traces contained in the ``SearchResult`` are returned as a stream. + +```java +final Stream stream = result.getTraces(); +stream.limit(5); +``` + + +## Logging + +The logging is provided by Log4j 2 with a SLF4J binding. The Log4j 2 SLF4J binding allows applications coded to the +SLF4J API to use Log4j 2 as the implementation. + +### Usage + +Here is an example illustrating how to log something with SLF4J. It begins by getting a logger with the name "LOG". This +logger is in turn used to log the message `I'm logging a variable 1234!`. + +```java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Example { + private static final Logger LOG = LoggerFactory.getLogger(Example.class); + + public void example() { + final int aNumber = 1234; + // logs to console: I'm logging a variable 1234! + LOG.info("I'm logging a variable {}!", aNumber); + } +} +``` + +### Customize logging + +It's easy to change the logging format with a file called `log4j2.xml`. If desired, this file must be in the `resources` +folder, for example `src/main/resources/log4j2.xml` + +```xml + + + + + + + + + + + + + +``` + +.. warning:: Be careful with logging sensitive information. + +.. note:: More information about customizing the logging can be found `here `_. + +.. note:: The default logger is pre-configured to log `INFO` to `STDOUT` (see the configuration above) + +.. note:: Log4j 2 supports various logging formats, including xml, yaml, json, properties, etc. + Currently, only the xml format is supported. + +.. note:: Contact your Hansken administrator for more information on where to find logs for your Hansken environment. + +## [EXPERIMENTAL FEATURE] Adding previews to a trace + +.. warning:: This is an experimental feature, which might change or get removed in future releases. + +Example: + +```java +public class ExamplePlugin extends ExtractionPlugin { + @Override + public PluginInfo pluginInfo(); + + @Override + public void process(final Trace trace, final DataContext context) { + final byte[] previewData; + // set the preview data for the image/png MIME-type + trace.set("preview.image/png", previewData); + trace.set("preview.image/png", previewData); + } +} +``` \ No newline at end of file diff --git a/0.7.0/_sources/dev/java/testing.md.txt b/0.7.0/_sources/dev/java/testing.md.txt new file mode 100644 index 0000000..f53a5e8 --- /dev/null +++ b/0.7.0/_sources/dev/java/testing.md.txt @@ -0,0 +1,156 @@ +# Using the Test Framework in Java + +.. _java testing: + +This section assumes you use the same setup as is used in the +[Extraction Plugin Examples](https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples). + +## Prerequisites + +Java Plugins can use the `plugin-super-pom` as maven parent, which makes sure the +FLITS [Test Framework](../concepts/test_framework.md) is included in the build. + +```xml + + + org.hansken.plugin.extraction + plugin-super-pom + 0.4.3 + +``` + +## Embedded Testing versus Remote Testing + +There are ways of integration testing a plugin with the Test Framework: + +* **Embedded testing**: Here the plugin is run directly from a JUnit test without using the gRPC layer. +* **Remote testing**: Here the test will start an ExtractionPluginServer that will serve the plugin. All communication + is between server and plugin is done using gRPC. + +See below for an example of each way of testing. +The [Extraction Plugin Examples](https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples) contains many +more examples. + +### Embedded Testing example + +Embedded tests can be run as a unit test. + +```java +import static nl.minvenj.nfi.flits.util.FlitsUtil.srcPath; + +import java.nio.file.Path; + +import org.hansken.plugin.extraction.api.ExtractionPlugin; +import org.hansken.plugin.extraction.test.EmbeddedExtractionPluginFlits; + +/** + * An integration test for MyPlugin. + */ +class MyPluginIT extends EmbeddedExtractionPluginFlits { + + @Override + protected ExtractionPlugin pluginToTest() { + // MyPlugin is a class implementing the ExtractionPlugin interface, + // with pluginInfo() and process() methods. + return new MyPlugin(); + } + + @Override + public Path testPath() { + // Provide the folder containing input files. For examples, see + // https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples. + return srcPath("integration/inputs"); + } + + @Override + public Path resultPath() { + // Provide the folder containing result files. For examples, see + // https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples. + return srcPath("integration/results"); + } + + @Override + public boolean regenerate() { + // Returning false means the test will fail if the result files don't + // match the outcome of the=++ test. Returning true means the test create + // new result files . + return false; + } +} +``` + +### Remote Testing example + +Note that the following example serves the plugin by using `ExtractionServer`. + +```java +import static nl.minvenj.nfi.flits.util.FlitsUtil.srcPath; + +import java.nio.file.Path; + +import org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginClient; +import org.hansken.plugin.extraction.runtime.grpc.server.ExtractionPluginServer; +import org.hansken.plugin.extraction.test.plugins.DataTransformationsPlugin; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; + +public class RemoteTransformationPluginFlitsIT extends RemoteExtractionPluginFlits { + + private static ExtractionPluginServer _server; + private static ExtractionPluginClient _client; + + @BeforeAll + public static void init() throws Exception { + final int port = 8999; + + // Serve MyPlugin. + // MyPlugin is a class implementing the ExtractionPlugin interface, with PluginInfo and Process methods. + _server = ExtractionPluginServer.serve(port, MyPlugin::new); + + // Create an ExtractionPluginClient + _client = new ExtractionPluginClient("localhost", _server.getListeningPort()); + } + + @AfterAll + public static void destruct() { + // At the end of the test, make sure the server and client are closed. + if (_server != null) { + _server.close(); + } + if (_client != null) { + _client.close(); + } + } + + @Override + public Path testPath() { + // Provide the folder containing input files. For examples, see https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples. + return srcPath("integration/inputs"); + } + + @Override + public Path resultPath() { + // Provide the folder containing result files. For examples, see https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples. + return srcPath("integration/results"); + } + + @Override + protected ExtractionPluginClient pluginToTest() { + // For Remote testing, the test won't talk directly to the plugin, but to the client. + // The client will use gRPC to communicate with the served plugin. + return _client; + } + + @Override + public boolean regenerate() { + // Returning false means the test will fail if the result files don't match the outcome of the test. + // Returning true means the test create new result files. + return false; + } +} +``` + +.. note:: Note that with a `RemoteTransformationPluginFlitsIT` it is possible to start a docker image of a plugin and + run remote tests against it using your own testdata. To do this, simply remove all `_server` code and manually start + your plugin in a docker container. Then run the test against the docker container by setting the correct url and + port, presumably `new ExtractionPluginClient("localhost", 8999)`. diff --git a/0.7.0/_sources/dev/python.rst.txt b/0.7.0/_sources/dev/python.rst.txt new file mode 100644 index 0000000..1bd6d44 --- /dev/null +++ b/0.7.0/_sources/dev/python.rst.txt @@ -0,0 +1,24 @@ +Python +====== + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + python/api_changelog + python/prerequisites + python/getting_started + python/packaging + python/snippets + python/testing + python/hanskenpy + python/debugging + +API Documentation +----------------- + +.. autosummary:: + :toctree: python/api + :recursive: + + hansken_extraction_plugin.api diff --git a/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.data_context.rst.txt b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.data_context.rst.txt new file mode 100644 index 0000000..e49fcfd --- /dev/null +++ b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.data_context.rst.txt @@ -0,0 +1,29 @@ +hansken\_extraction\_plugin.api.data\_context +============================================= + +.. automodule:: hansken_extraction_plugin.api.data_context + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + DataContext + + + + + + + + + diff --git a/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.extraction_plugin.rst.txt b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.extraction_plugin.rst.txt new file mode 100644 index 0000000..f6a862b --- /dev/null +++ b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.extraction_plugin.rst.txt @@ -0,0 +1,32 @@ +hansken\_extraction\_plugin.api.extraction\_plugin +================================================== + +.. automodule:: hansken_extraction_plugin.api.extraction_plugin + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + BaseExtractionPlugin + DeferredExtractionPlugin + ExtractionPlugin + MetaExtractionPlugin + + + + + + + + + diff --git a/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.extraction_trace.rst.txt b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.extraction_trace.rst.txt new file mode 100644 index 0000000..01a389b --- /dev/null +++ b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.extraction_trace.rst.txt @@ -0,0 +1,33 @@ +hansken\_extraction\_plugin.api.extraction\_trace +================================================= + +.. automodule:: hansken_extraction_plugin.api.extraction_trace + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + ExtractionTrace + ExtractionTraceBuilder + MetaExtractionTrace + SearchTrace + Trace + + + + + + + + + diff --git a/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.plugin_info.rst.txt b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.plugin_info.rst.txt new file mode 100644 index 0000000..a608c96 --- /dev/null +++ b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.plugin_info.rst.txt @@ -0,0 +1,33 @@ +hansken\_extraction\_plugin.api.plugin\_info +============================================ + +.. automodule:: hansken_extraction_plugin.api.plugin_info + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Author + MaturityLevel + PluginId + PluginInfo + PluginResources + + + + + + + + + diff --git a/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.rst.txt b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.rst.txt new file mode 100644 index 0000000..c5d104f --- /dev/null +++ b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.rst.txt @@ -0,0 +1,38 @@ +hansken\_extraction\_plugin.api +=============================== + +.. automodule:: hansken_extraction_plugin.api + + + + + + + + + + + + + + + + + + + +.. rubric:: Modules + +.. autosummary:: + :toctree: + :recursive: + + hansken_extraction_plugin.api.data_context + hansken_extraction_plugin.api.extraction_plugin + hansken_extraction_plugin.api.extraction_trace + hansken_extraction_plugin.api.plugin_info + hansken_extraction_plugin.api.search_result + hansken_extraction_plugin.api.trace_searcher + hansken_extraction_plugin.api.tracelet + hansken_extraction_plugin.api.transformation + diff --git a/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.search_result.rst.txt b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.search_result.rst.txt new file mode 100644 index 0000000..28a93a8 --- /dev/null +++ b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.search_result.rst.txt @@ -0,0 +1,29 @@ +hansken\_extraction\_plugin.api.search\_result +============================================== + +.. automodule:: hansken_extraction_plugin.api.search_result + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + SearchResult + + + + + + + + + diff --git a/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.trace_searcher.rst.txt b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.trace_searcher.rst.txt new file mode 100644 index 0000000..dd3e365 --- /dev/null +++ b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.trace_searcher.rst.txt @@ -0,0 +1,29 @@ +hansken\_extraction\_plugin.api.trace\_searcher +=============================================== + +.. automodule:: hansken_extraction_plugin.api.trace_searcher + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + TraceSearcher + + + + + + + + + diff --git a/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.tracelet.rst.txt b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.tracelet.rst.txt new file mode 100644 index 0000000..c502877 --- /dev/null +++ b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.tracelet.rst.txt @@ -0,0 +1,29 @@ +hansken\_extraction\_plugin.api.tracelet +======================================== + +.. automodule:: hansken_extraction_plugin.api.tracelet + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Tracelet + + + + + + + + + diff --git a/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.transformation.rst.txt b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.transformation.rst.txt new file mode 100644 index 0000000..3d80bab --- /dev/null +++ b/0.7.0/_sources/dev/python/api/hansken_extraction_plugin.api.transformation.rst.txt @@ -0,0 +1,31 @@ +hansken\_extraction\_plugin.api.transformation +============================================== + +.. automodule:: hansken_extraction_plugin.api.transformation + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + Range + RangedTransformation + Transformation + + + + + + + + + diff --git a/0.7.0/_sources/dev/python/api_changelog.md.txt b/0.7.0/_sources/dev/python/api_changelog.md.txt new file mode 100644 index 0000000..965f671 --- /dev/null +++ b/0.7.0/_sources/dev/python/api_changelog.md.txt @@ -0,0 +1,338 @@ +# Python API Changelog + +This document summarizes all important API changes in the Extraction Plugin API. This document only shows changes that +are important to plugin developers. For a full list of changes per version, please refer to the general +:ref:`changelog `. + +.. If present, remove `..` before `## |version|` if you create a new entry after a previous release. + +## |version| + +* Escaping the `/` character in matchers is optional. + This simplifies and aims for better HQL and HQL-Lite compatability. + See for more information and examples the :ref:`HQL-Lite syntax documentation`. + + Examples: + + * Old: `file.path:\/Users\/*\/AppData` -> new: `file.path:/Users/*/AppData` + * Old: `file.path:\\/Users\\/*\\/AppData` -> new: `file.path:/Users/*/AppData` + * Old: `registryEntry.key:\/Software\/Dropbox\/ks*\/Client-p` -> new: `registryEntry.key:/Software/Dropbox/ks*/Client-p` + +* Hansken returns `file.path` properties (outside the scope of matchers) as a `String` property, + instead of a list of strings. + Example: `trace.get('file.path')` now returns `'/dev/null'`, this was `['dev', 'null']`. + +* Improved plugin loading when using `serve_plugin` and `build_plugin`: + `import` statements now work for modules (python files) that are located the same directory structure of a plugin. + +## 0.6.1 + +* The docker image build script `build_plugin` has been updated to allow for extension of the docker command. + This can be especially handy for specifying a proxy. You should build your plugin container image with the following + command: + + ```bash + build_plugin PLUGIN_FILE DOCKER_FILE_DIRECTORY [DOCKER_IMAGE_NAME] [DOCKER_ARGS] + ``` + + .. warning:: Note that the `DOCKER_IMAGE_NAME` argument no longer requires a `-n` parameter to be specified. + + For usage read further in [packaging](packaging.md). + +## 0.6.0 + +.. warning:: This is an API breaking change. + Upgrading your plugin to this version will require code changes. + Plugins built with previous versions of the SDK from `0.3.0` will still work with Hansken. + +.. warning:: It is strongly recommended to upgrade your plugins to this new version because it significantly improves + the start-up time of Hansken. See the migration steps below. + +This release contains both build pipeline changes and API changes. +Please read all changes carefully. + +### Build pipeline change + +* Extraction plugin container images are now labeled with PluginInfo. This + allows Hansken to efficiently load extraction plugins. + Migration steps from earlier versions: + + 1. Update the SDK version in your `setup.py` / `requirements.txt` + 2. If you come from a version prior to `0.4.0`, or if you use a plugin name + instead of a plugin id in your `pluginInfo()`, switch to the plugin id style + (read instructions for version `0.4.0`) + 3. Update your build scripts to build your plugin (Docker) container image. + Be sure to [have the Extraction Plugins SDK installed](getting_started.md#Installation). + Then, you should build your plugin container image with the following command: + + ```bash + build_plugin PLUGIN_FILE DOCKER_FILE_DIRECTORY -n [DOCKER_IMAGE_NAME] + ``` + + For example: + ```bash + build_plugin plugin/chatplugin.py . -n extraction-plugins/chatplugin + ``` + + This will generate a plugin image: + + * The extraction plugin is added to your local image registry (`docker images`), + * Note that DOCKER\_IMAGE\_NAME is optional and will default to `extraction-plugin/PLUGINID`, e.g. + `extraction-plugin/nfi.nl/extract/chat/whatsapp`, + * The image is tagged with two tags: `latest`, and your plugin version. + + +### API changes + +* The field `plugin` has been removed from `PluginInfo`. +* The field `pluginId` should now be the first argument of PluginInfo (when using unnamed arguments). + + Old (unnamed arguments): + + ```python + def plugin_info(self): + return PluginInfo(self, '1.0.0', 'description', author, + MaturityLevel.PROOF_OF_CONCEPT, '*, 'https://hansken.org', + PluginId(...), 'Apache License 2.0') + ``` + + New (removed `self`, and moved `PluginId(...)` to first argument position): + + ```python + def plugin_info(self): + return PluginInfo(PluginId(...), '1.0.0', 'description', + author, MaturityLevel.PROOF_OF_CONCEPT, + '*', 'https://hansken.org', 'Apache License 2.0') + ``` + + Old (named arguments): + + ```python + def plugin_info(self): + return PluginInfo(plugin=self, + version='1.0.0', + ...) + ``` + + New (removed `plugin=self`): + + ```python + def plugin_info(self): + return PluginInfo(version='1.0.0', + ...) + ``` + +* Plugin `data_context.data_size` is now a variable instead of a method: + + Old: + + ```python + def process(self, trace: ExtractionTrace, data_context: DataContext): + size = data_context.data_size() + ``` + + New: + + ```python + def process(self, trace: ExtractionTrace, data_context: DataContext): + size = data_context.data_size + ``` + +* Simplify declaring required runtime resources in a plugin's info. + + Extraction plugin resources don't use the builder pattern anymore. + + Old: + + ```python + return PluginInfo( + ..., + resources=PluginResources.builder().maximum_cpu(0.5).maximum_memory(1000).build()) + ) + ``` + + New: + + ```python + # no need for a builder, declare resources by direct instantiation + return PluginInfo( + ..., + resources=PluginResources(maximum_cpu=2.0, maximum_memory=2048) + ) + # or, as before, specify just on resource + return PluginInfo( + ..., + resources=PluginResources(maximum_memory=4096) + ) + ``` + +## 0.5.1 + +* Simplify tracelet properties by making the tracelet type prefix optional. + + ```python + # using a Tracelet object + trace.add_tracelet(Tracelet("prediction", { + "type": "example", + "confidence": 0.8 + })) + # or without a Tracelet object + trace.add_tracelet("identity", {"name": "John Doe", "status": "online"}) + ``` + +* Enabled _manual_ plugin testing, as described on :ref:`advanced use of the test framework in Python`. + +## 0.5.0 + +* Support vector data type in trace properties. + + ```python + embedding = Vector.from_sequence((width, height)) + tracelet = Tracelet("prediction", { + "prediction.type": "example-vector", + "prediction.embedding": embedding + }) + trace.add_tracelet(tracelet) + ``` + +## 0.4.13 + +* When writing input search traces for tests, it is no longer required to explicitly set an `id` property. + These are automatically generated when executing tests. + +## 0.4.7 + +* More `$data` matchers are supported in Hansken.py plugin runner. Before this improvement it was only possible to match + on `$data.type`. Now it is also possible to match for example on `$data.mimeType` and `$data.mimeClass`. The `$data` + matcher should still be at the end of the query as before. + +## 0.4.6 + +* It is now possible to specify maximum system resources in the `PluginInfo`. To run a plugin with 0.5 cpu (= 0.5 + vCPU/Core/hyperthread) and 1 gb memory, for example, the following configuration can be added to `PluginInfo`: + + ```python + plugin_info = PluginInfo(..., + resources=PluginResources.builder().maximum_cpu(0.5).maximum_memory(1000).build()) + ``` + +## 0.4.0 + +* Extraction Plugins are now identified with a `PluginInfo.PluginId` containing a domain, category and name. The + method `PluginInfo.name(pluginName)` has been replaced by `PluginInfo.id(new PluginId(domain, category, name)`. More + details on the plugin naming conventions can be found at the :doc:`../concepts/plugin_naming_convention` section. + +* `PluginInfo.name()` is now deprecated (but will still work for backwards compatibility). + +* A new license field `PluginInfo.license` has also been added in this release. + +* The following example creates a PluginInfo for a plugin with the name `TestPlugin`, licensed under + the `Apache License 2.0` license: + + ```python + class TestPlugin(ExtractionPlugin): + def plugin_info(self) -> PluginInfo: + return PluginInfo(self, + version='1.0.0', + description='A plugin for testing.', + author=Author('The Externals', 'tester@holmes.nl', 'NFI'), + maturity=MaturityLevel.PROOF_OF_CONCEPT, + webpage_url='https://hansken.org', + matcher='file.extension=txt', + id=PluginId(domain='nfi.nl', category='test', name='TestPlugin'), + license='Apache License 2.0' + ) + ``` + +## 0.3.0 + +* Extraction Plugins can now create new datastreams on a Trace through data transformations. Data transformations + describe how data can be obtained from a source. + + An example case is an extraction plugin that processes an archive file. The plugin creates a child trace per entry in + the archive file. Each child trace will have a datastream that is a transformation that marks the start and length of + the entry in the original archive data. By just describing the data instead of specifying the actual data, a lot of + space is saved. + + Although Hansken supports various transformations, the Extraction Plugins SDK for now only supports ranged data + transformations. Ranged data transformations define data as a list of ranges, each range with an offset and length in + a bytearray. + + The following example sets a new datastream with dataType `html` on a trace, by setting a ranged data transformation: + + ```python + trace.add_transformation('html', RangedTransformation(Range(offset, length))) + ``` + + The following example creates a child trace and sets a new datastream with dataType `raw` on it, by setting a ranged + data transformation with two ranges: + + ```python + child = trace.child_builder('new trace') + child.add_transformation('raw', RangedTransformation.builder() + .add_range(10, 20) + .add_range(50, 30) + .build()) + }); + ``` + + More detailed documentation will follow in an upcoming SDK release. + +## 0.2.0 + +.. warning:: This is an API breaking change. + Plugins created with an earlier version of the extraction plugin + SDK are not compatible with Hansken that uses `0.2.0` or later. + +* Introduced a new extraction plugin type `api.extraction_plugin.DeferredExtractioPlugin`. + Deferred Extraction plugins can be run at a different extraction stage. + This type of plugin also allows accessing other traces using the searcher. + +* The class `api.extraction_context.ExtractionContext` has been renamed to `api.data_context.DataContext`. + The new name `DataContext` represents the class contents better. + Plugins have to update matching import statements accordingly. + Plugins should also update the named argument `context` to `data_context` of the plugin `process()` method. + This change has no functional changes. + + Old: + + ```python + from hansken_extraction_plugin.api.extraction_context import ExtractionContext + + def process(self, trace, context): + pass + ``` + + New: + + ```python + from hansken_extraction_plugin.api.data_context import DataContext + + def process(self, trace, data_context): + pass + ``` + +* Moved `api.author.Author` to `api.plugin_info.Author`, and moved `api.maturity_level.MaturityLevel` + to `api.plugin_info.MaturityLevel` + This is a more *pythonic* way of grouping of classes into modules. This change has no functional side effects. + + Plugins have to update matching import statements accordingly. + + Old: + + ```python + from hansken_extraction_plugin.api.author import Author + from hansken_extraction_plugin.api.maturity_level import MaturityLevel + from hansken_extraction_plugin.api.plugin_info import PluginInfo + ``` + + New: + + ```python + from hansken_extraction_plugin.api.plugin_info import Author, MaturityLevel, PluginInfo + ``` + +* Removed `DataContext.get_first_bytes()` from the public API. + +* Removed `api.extraction_trace.validate_update_arguments(..)` from the public API. This method is still invoked + implicitly when setting trace properties. diff --git a/0.7.0/_sources/dev/python/debugging.md.txt b/0.7.0/_sources/dev/python/debugging.md.txt new file mode 100644 index 0000000..a109ec0 --- /dev/null +++ b/0.7.0/_sources/dev/python/debugging.md.txt @@ -0,0 +1,162 @@ +# How to debug an Extraction Plugin + +Debugging is the art of removing bugs — hopefully quickly. + +## Locally + +To debug a plugin locally, it is recommended to start the plugin via the IDE. This has the advantage that breakpoints +can easily be put in the code instead of printing log statements, for example. To start a plugin locally, a piece of +code must be added, see [Testing](testing.md) for more information. + +### Logging + +The logging of the extraction plugin is displayed in the console. + +## Locally with Docker + +Debugging an extraction plugin via docker is a bit trickier. In order to debug in Python, a debugger must be added to +the extraction plugin. There are several debug modules for Python available, but one debug module that works well with +Visual Studio Code is [debugpy](https://github.com/microsoft/debugpy). This package is developed by Microsoft +specifically for use in Visual Studio Code with Python. + +.. note:: `debugpy` implements the Debug Adapter Protocol (DAP), which is a standardised way for development tools to + communicate with debuggers. + +Using `debugpy` with Docker containers requires 4 distinct steps: + +1. Install `debugpy` +2. Configuring `debugpy` in Python +3. Build a docker image +4. Configuring the connection to the Docker container +5. Setting breakpoints in your code + +### Install `debugpy` + +First, add `debugpy` to your `setup.py`. + +```python +from setuptools import setup + +setup( + # ... + install_requires=[ + "hansken-extraction-plugin==0.4.7", # the plugin SDK + "debugpy==1.5.1" + ] +) +``` + +### Configuring `debugpy` in Python + +At the beginning of your script, import `debugpy`, and call `debugpy.listen()` to start the debug adapter, passing +a `(host, port)` tuple as the first argument. Use the `debugpy.wait_for_client()` function to block program execution +until the client is attached. + +```python +import debugpy + +debugpy.listen(("0.0.0.0", 5678)) +debugpy.wait_for_client() # blocks execution until client is attached + +# your extraction plugin code +``` + +### Build a Docker image + +If the Docker image is not built, first build the image as described +[here](getting_started.md#Building a docker image for a Python plugin). + + +### Configuring the connection to the Docker container + +`debugpy` is now set up to accept connections inside a Docker container. To connect to `debugpy` in the docker +container, port 5678 must be published. To make a port available to services outside of Docker, use the --publish or -p +flag. This creates a firewall rule which maps a container port to a port on the Docker host to the outside world. + +To run the extraction plugin with the published port the following command can be used: + +```bash +docker run -p 5678:5678 your_extraction_plugin_name +``` + +The next step is to configure Visual Studio Code. A `launch.json` file must be created in order for Visual Studio Code +to connect to the extraction plugin in Docker. This minimal `launch.json` example below tells the debugger to attach +to `localhost` on port `5678`. + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Remote Attach", + "type": "python", + "request": "attach", + "connect": { + "host": "localhost", + "port": 5678 + }, + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "." + } + ] + } + ] +} +``` + +### Setting breakpoints in the code + +The last step is to add breakpoints in the code. + +### Logging in Docker + +The logging of the extraction plugin is displayed in the console after running the `docker run` command. In addition, +the logging is also displayed in the Visual Studio Code console while debugging. + +## Kubernetes + +In kubernetes it is currently _not_ possible to debug via `debugpy` because no debug ports are published. + +### Logging in Kubernetes + +If there is authorization to the kubernetes cluster, the logging can be viewed with the following command: + +```bash +kubectl logs -f hansken-extraction-plugins/your_extraction_plugin_pod +``` + +## Debug HQL + +An HQL query can be debugged by running the test framework with the `--verbose` option enabled. The found HQL matches +will then be displayed in the console. To test a plugin in python with the `--verbose` option enabled use the following +command: + +```bash +test_plugin --standalone plugin/your_plugin.py --regenerate --verbose +``` + +The following output will then be displayed in the console: + +```text +HQL match found for: +$data.type=jpg +With trace: +dataType=jpg +types={file, data} +properties={data.raw.mimeType=image/jpg, path=/test-input-trace, file.name=image.jpg, name=test-input-trace, id=0} +``` + +If the HQL query contains an error, it will be shown in the generated test results. An example of an invalid query +is ``$data.mimeType=image/jpg`` (slash not escaped). This query will produce an error like the one shown below. + +```json +{ + "class": "org.hansken.plugin.extraction.hql_lite.lang.ParseException", + "message": "HqlLiteHumanQueryParser: line 1:20 token recognition error at: '/jpg'" +} +``` + +.. note:: The error is only shown in the generated trace, so to find out the `ParseException` run the ``test_plugin`` + command with the ``--regenerate`` option enabled. diff --git a/0.7.0/_sources/dev/python/getting_started.md.txt b/0.7.0/_sources/dev/python/getting_started.md.txt new file mode 100644 index 0000000..bfced1e --- /dev/null +++ b/0.7.0/_sources/dev/python/getting_started.md.txt @@ -0,0 +1,195 @@ +# Getting started + +Set up a development environment: step by step. + +The following section describes how to set up a fully working development environment for extraction plugins with Python. +This is written for those who are not comfortable setting up a working build environment. +This is **optional**; advanced users may choose a different development environment setup, and can skip this section completely. + +If you fail to set up a development environment, feel free to ask for help at our Discord channel. + + +## Install required software on Ubuntu + +In order to be able to develop Hansken Extraction Plugins in Python on Ubuntu, the following build tools need be installed on your system: python, pip, tox, Java, Docker. + +* **Python, pip, tox, Java** + To install, run the following commands in your terminal: + + ```bash + sudo apt update + sudo apt install python3.8 python3-pip tox default-jdk + ``` + + You can check whether all versions are installed correctly by running the following commands (and validate the output): + ```bash + python --version + # should return version 3.8.10 or higher + + pip3 --version + # should return version 20.0.2 or higher + + tox --version + # should return version 3.11.2 or higher + + java -version + # should return version 11.0.4 or higher + ``` + +If the above software is installed, you can continue with "Install Docker", +or if you don't want to install Docker, continue with "Install your IDE: Pycharm". + + +## Install required software on Windows. + +In order to be able to develop Hansken Extraction Plugins in Python on Windows, follow the next steps: + +* For verification of the installation of each program we will use commands on the command prompt. + This can be opened by clicking the Windows Start button and typing: + + ```bash + cmd + ``` + + Then hit Enter . This will open the command prompt where you can enter the commands given in the following steps. + +* **Python 3.8 or higher & pip** + + Download the installer from [python.org/Downloads](https://www.python.org/downloads/) (click the yellow "Download Python" button) + and run it to install Python and pip. + Pip is the standard package manager for Python. + It allows you to install and manage additional packages that are not part of the Python standard library, like the Extraction Plugins SDK. + + Be sure to select the option "Add python to PATH". + + When the installation is complete, verify the installation by checking the Python and pip versions: + + ```bash + python --version + # should return your downloaded Python version (>3.8.5) + + pip3 --version + # should return 20.2.3 or higher + ``` + +* **tox** + Tox is used to automate and standardize testing in Python. Use Pip to install Tox with this command: + + ```bash + pip install tox + ``` + + Verify that Tox is installed by running: + + ```bash + tox --version + # should return 3.23.0 or higher + ``` + +* **Java JDK 11.0.4 (or higher)** + Java JDK 11.0.4 or higher is needed to run the test framework of the Extraction Plugins SDK. + This enables you to test without actually deploying the plugin in Hansken. + Installing Java on Windows can be done in many different way, but for now we can not recommend one method. + Please have a look at [Install the Microsoft Build of OpenJDK](https://docs.microsoft.com/en-us/java/openjdk/install) for more details. + + ```bash + java -version + # should return 11.0.4 or higher + ``` + + N.b. Make sure to set the environment variable in case the installed JDK is not detected by the system. + You can follow the below-mentioned steps to set the environment variable: + + 1. Click the Windows start button + 2. Type "advanced system settings" + 3. Hit enter + 4. Now click on Environment Variables, select Path under System Variables section and click on Edit. We need to add the path of installed JDK to system Path. + 5. Click on New Button and add the path to installed JDK bin which is C:\java\java-11\jdk-11.0.4\bin in our case. + 6. Press OK Button 3 times to close all the windows. This sets the JDK 11 on system environment variables to access the same from the console. + + +If the above software is installed, you can continue with "Install Docker", +or if you don't want to install Docker, continue with "Install your IDE: Pycharm". + +## Install Docker (Ubuntu, Windows) +Installing Docker is a bit more complicated. The Docker website describes the installation instructions in detail. +Please follow the instructions on [docs.docker.gom/get-docker/](https://docs.docker.com/get-docker/) to install docker. + +* To check you have Docker installed correctly, run + + ```bash + docker --version + # should return version 20.10.17 or higher + ``` + +Note: if you run Docker inside a managed network, you might also need to configure proxies and/or certificates. +Please contact your system administrator if you need help with this. + + +## Set up your IDE: PyCharm + +We recommend that you use an IDE to aid you in your development of Extraction Plugins. PyCharm is a good choice. + +* JetBrains has an excellent installation guide on their webpage: [Click here to go to the Pycharm Installation Guide](https://www.jetbrains.com/help/pycharm/installation-guide.html) + + +## Download an extraction plugin template (empty plugin) +You can download an extraction plugin template. +This is an empty plugin, from which you can rapidly start your plugin development. + +* The template is hosted on GitHub: https://github.com/NetherlandsForensicInstitute/hansken-extraction-plugin-template-python. + You can download a zip with the template from here. The below screenshot shows where the download button is located: + + ![](getting_started/download_template.png) + + +## Import the Extraction Plugins Skeleton in PyCharm + +* First unzip the skeleton plugin downloaded from the previous step. +* Next, start PyCharm. +* When PyCharm starts, choose "Open" and select the folder where you placed the Extraction Plugin Skeleton. +![](getting_started/pycharm_open_project.png) + +* The following popup will appear, click OK . +![](getting_started/pycharm_open_project_venv.png) + +* The Extraction Plugins Skeleton is now loaded in PyCharm, which should look as follows: +![](getting_started/pycharm_project_start_page.png) + +* Be sure to give the `README.md` a read when you are done with the Prerequisites. + + +## Verify full setup +To verify that your system has been setup correctly, you can run the test suite in the Extraction Plugin Skeleton: + +* First, press `alt-F12` at the same time to open a terminal in PyCharm in the project root folder. +* To run the tests of the Skeleton, run this command in the terminal from the root folder: + ```bash + tox + ``` + The first time running tox may take a few minutes! Please be patient 😊 + + Tox will install all required plugin dependencies, and start your tests. + N.b. The plugin template demonstrates how to build a plugin with tox. + Some plugin developers choose a different tool than tox. + +* If your system has been set up correctly, the output should end with a summary like this: + + ``` + py38: commands succeeded + congratulations :) + ``` + + This means the setup is finished. You now have everything installed to start coding your own plugin! + + +## Next steps + +Now that you have a working environment, you can start doing cool stuff. +Please have a look at the following pages for more information: + +* [Packaging](packaging.md): how to package your plugin and use it in Hansken +* [Run plugins with Hansken.py](hanskenpy.md): run your plugin on a case without uploading the plugin to Hansken, useful for quick prototyping +* [Testing](testing.md): how to write tests for your plugin +* [Debugging](debugging.md): if your plugin isn't working as expected, you can debug it +* [Snippets](snippets.md): code snippets to demonstrate common plugin usage patterns diff --git a/0.7.0/_sources/dev/python/hanskenpy.md.txt b/0.7.0/_sources/dev/python/hanskenpy.md.txt new file mode 100644 index 0000000..32d3022 --- /dev/null +++ b/0.7.0/_sources/dev/python/hanskenpy.md.txt @@ -0,0 +1,91 @@ +# Run plugins with Hansken.py + +Hansken.py is a Python client to Hansken's REST API, developed and maintained by the Netherlands Forensic Institute. + +With Hansken.py, you can run your Python plugin on a project on your Hansken installation that has already been +extracted. This way of running your plugin is useful during your plugin development. It is not required to upload your +plugin to Hansken and start an extraction, making the development cycle faster. However, please note that this is only +useful during the development stage of your plugin, as the execution of your plugin will be much slower compared to +running the plugin during a Hansken extraction. + +.. note:: The execution of your plugin will be much slower in Hansken.py compared to running the plugin during a Hansken + extraction. + +## How to run python extraction plugins standalone with Hansken.py + +Running python extraction plugins standalone with Hansken.py is easy. It is just one command. This section explains how +to setup this command for your specific environment. + +### Create a runner file + +Create a file `run_with_hansken.py` in the root folder of your plugin. This will aid you in running the plugin with +hansken.py. + +```python +from hansken_extraction_plugin.runtime.extraction_plugin_runner import run_with_hanskenpy + +from plugin.my_plugin import MyPlugin + +if __name__ == '__main__': + run_with_hanskenpy(MyPlugin) +``` + +### Preparing for the command + +Before you can enter the command that runs your extraction plugin with Hansken.py, you need to find three values: + +* `HANSKEN_PROJECT_ID` project id on which you want to run your plugin +* `YOUR_GATEKEEPER_URL` the URL to the Hansken gatekeeper +* `YOUR_KEYSTORE_URL` the URL to your keystore + +The correct values of these variables can be found in the Expert UI. Go to the search-page of your project in the +ExpertUI. Next to the search-bar hit the button "Save query". + +![hanskenpy_save_query.png](hanskenpy_save_query.png) + +A new dialog shows up. At the bottom of this dialog, you will find your gatekeeper and keystore urls as well as the +project id. + +![hanskenpy_gatekeeper_keystore.png](hanskenpy_gatekeeper_keystore.png) + +### Running your plugin with Hansken.py + +Next, open a terminal in the project root folder of your plugin and enter the following command. It will run your +extraction plugin with Hansken.py in Hansken. Replace the three variables with their respective values. + +```bash +python3 ./run_with_hanskenpy.py -v -l - HANSKEN_PROJECT_ID --endpoint YOUR_GATEKEEPER_URL --keystore YOUR_KEYSTORE_URL +``` + +If your command runs well, you might be prompted for your username and password. There will be some output (note that +the output may vary depending on your system setup and project content): + +```text +[2021-03-16 12:59:45.344248+0000] INFO: hansken.auth: selected IDP ID (...) with SOAP endpoint (...) +[2021-03-16 12:59:45.344450+0000] WARNING: hansken.auth: IDP url known, user+pass auth required but no username supplied +username []: testaccount +[2021-03-16 12:59:48.423245+0000] INFO: hansken.auth: user acknowledged environment username or supplied custom username: testaccount +password for user testaccount: +[2021-03-16 12:59:53.799668+0000] INFO: hansken.auth: identity provider url and user+pass provided or known, using Keycloak SAML with Basic auth +[2021-03-16 12:59:53.805538+0000] INFO: hansken_extraction_plugin.runtime.extraction_plugin_runner: PluginRunner is running plugin class Plugin +[2021-03-16 12:59:53.859299+0000] INFO: hansken.auth: posting SAML request with authorization for user testaccount to IDP endpoint (...) +[2021-03-16 12:59:54.240290+0000] INFO: plugin.extraction_plugin: processing trace 54197e67-8135-40c3-93f1-3d73a5552693 +[2021-03-16 12:59:54.240753+0000] INFO: plugin.extraction_plugin: processing trace OCRimage +[2021-03-16 12:59:54.240753+0000] INFO: plugin.extraction_plugin: processing trace (...) +``` + +Note that the arguments `-v` and `-l -` are passed to enable logging. To find out what other options can be passed to +this command, please have a look at the hansken.py documentation, or simply run the following command: + +```bash +python3 ./run_with_hanskenpy.py --help +``` + +## Compatibility + +At this moment, running Extraction Plugins with Hansken.py has a few limitations. These are: + +* When writing an Extraction Plugin for use with Hansken.py, the matcher must contain exactly one "$data.property = + value" expression. +* [Data transformations](../concepts/data_transformations.md) are currently not supported by Hansken.py. +* :ref:`Tracelets` are not yet supported by the SDK in use with Hansken.py. diff --git a/0.7.0/_sources/dev/python/packaging.md.txt b/0.7.0/_sources/dev/python/packaging.md.txt new file mode 100644 index 0000000..272a503 --- /dev/null +++ b/0.7.0/_sources/dev/python/packaging.md.txt @@ -0,0 +1,43 @@ +# Packaging + +Extraction plugins are packaged as OCI images (also known as Docker images). +The OCI images are _labeled_ with the PluginInfo. +To automate packaging of a Python plugin and labeling the OCI image, +the Extraction Plugin SDK comes with a utility application `build_plugin`. + +Make sure that the Extraction Plugins SDK is [installed](getting_started.md#Installation) as well as Docker. + +Then, build your plugin container image using the following command: + +```bash +build_plugin PLUGIN_FILE DOCKER_FILE_DIRECTORY [DOCKER_IMAGE_NAME] [DOCKER_ARGS] +``` + +For example: +```bash +build_plugin chatplugin.py . chatplugin --build-arg http_proxy="$http_proxy" --build-arg https_proxy="$https_proxy" +``` + +This will generate a plugin image: +* The extraction plugin is added to your local image registry (`docker images`), +* Note that the variables `$http_proxy` and `$https_proxy` are put in quotes, this is needed in case they contain + spaces, +* The image is tagged with two tags: `latest`, and your plugin version. + +Arguments: +* PLUGIN_FILE: Path to the python file of the plugin. +* DOCKER_FILE_DIRECTORY: Path to the directory containing the Dockerfile of the plugin. +* (Optional) [DOCKER\_IMAGE\_NAME]: Name of the docker image without tag. Note that docker image names cannot start with + a period or dash. If it starts with a dash, it will be interpreted as an additional docker argument (see + DOCKER\_ARGS). If no name is given the name defaults to `extraction-plugin/PLUGINID`, e.g. + `extraction-plugin/nfi.nl/extract/chat/whatsapp`. +* (Optional) [DOCKER\_ARGS]: Additional arguments for the docker command, which can be as many arguments as you like. + +To verify that the image has been built, type the following command to view all local images: +```bash +docker images +``` + +Once your plugin is packaged, it can be published or 'uploaded' to Hansken. +See ":ref:`upload_plugin`" for instructions. + diff --git a/0.7.0/_sources/dev/python/prerequisites.md.txt b/0.7.0/_sources/dev/python/prerequisites.md.txt new file mode 100644 index 0000000..ba367ae --- /dev/null +++ b/0.7.0/_sources/dev/python/prerequisites.md.txt @@ -0,0 +1,9 @@ +# Prerequisites + +All required project dependencies to build extraction plugins are published on the public [PyPI](https://pypi.org/project/hansken-extraction-plugin/). + +Required: + +* Python 3.8 or higher +* Java 11 (for running the test-framework, which is implemented in Java) +* Docker (for packaging and deploying extraction plugins in containers, can be omitted if you have an external build pipeline that provides Docker) diff --git a/0.7.0/_sources/dev/python/snippets.md.txt b/0.7.0/_sources/dev/python/snippets.md.txt new file mode 100644 index 0000000..921e82e --- /dev/null +++ b/0.7.0/_sources/dev/python/snippets.md.txt @@ -0,0 +1,242 @@ +# Python code snippets + +## Adding properties to a trace + +Use :py:meth:`update ` +to add trace types and their properties to an +:py:class:`ExtractionTrace `. +Example: + +```python +def process(self, trace, data_context): + # get the name of the file + file_name = trace.get('file.name') + # set the chat application property on the trace + trace.update('chatConversation.application', f'DemoApp {file_name}') +``` + +All types and properties that can be set are defined in the :ref:`Hansken trace model`. + +### Date properties + +When adding a property which holds a value of data-type Date, always define timezone as being UTC. Example: + +```python +def process(self, trace, data_context): + trace.update('file.modifiedOn', + datetime.fromtimestamp(1630510809, tz=timezone.utc)) +``` + + +### Category for extra properties + +If the information, which must be added as a property, does not match any of the existing properties of Hansken trace +model, use the category "misc" (miscellaneous). When part of the category "misc", any name can be given to a property. +The values of miscellaneous properties are expected to be of data-type string. Example: + +```python +def process(self, trace, data_context): + trace.update({ + 'file.misc.notes': 'Some additional notes about the file trace.', + 'file.misc.anyName': 'Even more notes.' + }) +``` + +.. _tracelets python: + + +### Adding tracelets + +In the following Python example, a "prediction" :ref:`tracelet` is added to a trace. The tracelet consists +of a list of four properties, namely "class", "confidence", "modelName" and "modelVersion". + +```python +trace.add_tracelet(Tracelet('prediction', {'class': 'telephone', + 'confidence': 0.8, + 'modelName': 'yolo', + 'modelVersion': '2.0'})) +``` + + +## Adding child traces to a trace + +Adding child traces to the trace can be done by creating a builder with +:py:meth:`child_builder `. +Example: + +```python +def process(self, trace, data_context): + child_builder = trace.child_builder('childTrace-1') + child_builder.update({ + 'chatMessage.application': 'DemoApp', + 'chatMessage.from': 'Ann', + 'chatMessage.to': ['Mark'], + # list, because there can be multiple receivers + 'chatMessage.message': 'Hello, are you there?', + }).build() + grandchild_builder = child_builder.child_builder('grandchild') + grandchild_builder.update(data={'byte': b'some bytes'}) + grandchild_builder.build() +``` + +This adds a single child trace with name `childTrace-1` with four properties and a grandchild trace with name +`grandchild` and a byte data stream. + +.. _datastreams python: + + +## Adding data to a trace + +Traces can have data attached to them. See :ref:`datastreams` for more information. +The following two snippets demonstrate how to add data to a trace. + +It is currently not possible to verify that a specific data stream is already set or not. + + +### Data Transformations + +The most efficient way to add data to a trace is using data transformations. +See :doc:`../concepts/data_transformations` for more details. + +The following example sets a new datastream with dataType `html` on a trace, by setting a ranged data transformation: + +```python +trace.add_transformation('html', RangedTransformation(Range(offset, length))) +``` + +The following example creates a child trace and sets a new datastream with dataType `raw` on it, by setting a ranged +data transformation with two ranges: + +```python +child = trace.child_builder('new trace') +child.add_transformation('raw', RangedTransformation.builder() + .add_range(10, 20) + .add_range(50, 30) + .build()) +}); +``` + + +### Blobs + +It is not always possible to create a transformation for the data that has to be +added to a trace. For example, if the data is a result of a computation, and not +a direct subset of another data stream.. + +The following snippet shows how to create a new data stream of dataType `raw` on a trace from a blob stored in `bytes`: + +```python +data = {'raw': b'...'} +trace.update(data=data); +``` + +#### Streaming data + +.. warning:: Streaming data does not work with the Hansken.py runner because Hansken.py does not support it. It does + work when running your plugin in Hansken and in the test framework. + +When dealing with large quantities of data, it is possible to keep the memory usage +of the plugin within manageable limits by streaming the data from the plugin to Hansken in smaller chunks. +To do this, use the `with trace.open(data_type=..., mode='wb')` syntax. Here are some examples: + +Stream strings to `raw` (default) datastream: + +```python +with trace.open(mode='wb') as writer: + writer.write(b'a string') + writer.write(bytes(another_string, 'utf-8')) +``` + +Stream a BufferedReader object to a `text` datastream: + +```python +with trace.open(data_type='text', mode='wb') as output, open('input.text', 'rb') as in_file: + output.write(in_file) +``` + +## Specifying system resources + +It is possible to specify **maximum** system resources in the `PluginInfo`. To run a plugin with 0.5 cpu (= 0.5 +vCPU/Core/hyperthread) and 1 gb memory, for example, the following configuration can be added to `PluginInfo`: + +```python +plugin_info = PluginInfo(..., + resources=PluginResources(maximum_cpu=0.5, maximum_memory=1000)) +``` + +.. _python_snippets_deferred: + +## Deferred Plugins + +Implementing a deferred extraction plugin requires inheriting the +:py:class:`DeferredExtractionPlugin ` +base class. + +```python +class DeferredPlugin(DeferredExtractionPlugin): + def process(self, trace, context, searcher): +``` + +This allows accessing a third :py:class:`TraceSearcher ` +parameter in the process function. This can be used to search for traces: + +```python +with searcher.search('file.extension:html', 10) as searchresult: + for trace in searchresult: + log.debug(f'extension {trace.get("file.extension")}') +``` + +The search method accepts two arguments; a HQL query and the maximum number of traces the return. The ``search`` method +accepts an HQL query and a count, which represents the maximum number of traces to return. + +It may be useful to specifically search for traces from the image being extracted. Add ``"image:" + trace.get("image")`` +to your query. The query of the provided example could be extended like +this: `"file.extension:html AND image:" + trace.get("image")`. + +The returned :py:class:`SearchResult ` +should be closed, for example by using `with`. The resulting search result is an iterable, which will be exhausted when +no more traces are available. The search result allows taking one or more traces by calling :py: +meth:`take ` or +:py:meth:`takeone `. + + +## Logging + +We use Logbook to log messages in Python. Logbook is a logging system for Python that replaces the standard library’s +logging module. + +To enable logging in your plugin, add the following to the top of your plugin code: + +```python +from logbook import Logger + +log = Logger(__name__) +``` + +From there on the logging is pretty straight forward: + +```python +log.info(f'Logging a variable: {my_variable}') +``` + +The default log level is WARNING. You can use the `-v` (or `-vv` or `-vvv`) option of `serve_plugin.py` to increase the +log level. This is typically done in the plugin `Dockerfile`. + +.. warning:: Be careful with logging sensitive information. + +.. note:: Contact your Hansken administrator for more information on where to find logs for your Hansken environment. + +## [EXPERIMENTAL FEATURE] Adding previews to a trace + +.. warning:: This is an experimental feature, which might change or get removed in future releases. + +Use :py:meth:`update ` +to add previews to an +:py:class:`ExtractionTrace `. +Example: + +```python +def process(self, trace, data_context): + # set the preview data for the image/png MIME-type + trace.update('preview.image/png', b'\x00\xff') +``` \ No newline at end of file diff --git a/0.7.0/_sources/dev/python/testing.md.txt b/0.7.0/_sources/dev/python/testing.md.txt new file mode 100644 index 0000000..2bc0a87 --- /dev/null +++ b/0.7.0/_sources/dev/python/testing.md.txt @@ -0,0 +1,108 @@ +# Advanced use of the Test Framework in Python + +.. _python testing: + +This section assumes you use the same setup as is used in +the [Extraction Plugin Examples](https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples). + +By default, the build scripts as described in the [Getting Started](getting_started.md) section will automatically run +tests. The appropriate commands have been added to the tox.ini directly. This section gives a little more detail on the +test commands and options. + +One can simply create unit tests for a plugin directly. However, we also provide a test-framework for testing them over +gRPC. The test-framework serves a running instance of a Python plugin, and feeds it input files and compares the results +against an expected result set. + +Note that the test-framework is implemented in Java, hence the Java 11 requirement. A jar file is included in the Python +SDK which is called from a Python wrapper. + +## Regenerate expected test results + +The build and test scripts run some integration tests. To update the expected test outcome, the following command can be +used: + +```bash +tox -e regenerate +``` + +## Standalone testing + +The test runner is a script called `test_plugin` which is available in the SDK. + +To get started, `cd` into the directory of the plugin you want to test and run: + +```bash +test_plugin --standalone plugin/chat_plugin.py +``` + +Note that the argument provided to the option `--standalone` must be the relative path to the plugin `py` file which is +to be tested. This test accepts input files from the directory `testdata/input` and compares the results to the result +files in found in `testdata/results`. Use the optional argument `--regenerate` to regenerate the expected results for +the test when needed. + +This standalone test is also used by the `tox.ini` file to validate the plugin. Simply calling `tox` should be enough to +install all dependencies and run the test. + +## Testing with a Docker image + +If there is a docker image available for the plugin you can also test it by executing: + +```bash +test_plugin --docker extraction-plugin-examples-my-plugin +``` + +Replace the 'extraction-plugin-examples-chat' with the docker image you want to test. Run the following command to see +which docker images are available: + +```bash +docker images +``` + +## Manual testing + +The third option for testing is a manually started plugin. Start the plugin service in a terminal by executing: + +```bash +serve_plugin -vvv plugin/my_plugin.py +``` + +This will spin up the chat plugin at port 8999. Here also the argument must be a path to the plugin's `.py` file. In +another terminal window, run the test with: + +```bash +test_plugin --manual localhost 8999 +``` + +## Tip: Start tests in your IDE +To start the extraction plugin from code, create a `__main__` method which calls the `_test_validate_standalone` +method of the test framework (see the example below). This method causes the extraction plugin to be started and +supplied with data by the FLITS test framework. In this way the test can be started from the IDE, which has the +advantage that it is easier to debug. + +```python +from hansken_extraction_plugin.test_framework.test_plugin import _test_validate_standalone +from hansken_extraction_plugin.api.extraction_plugin import ExtractionPlugin + + +class PluginToTest(ExtractionPlugin): + + def plugin_info(self): + # return plugin info + pass + + def process(self, trace, data_context): + # process the data/trace here + pass + + +if __name__ == '__main__': + _test_validate_standalone(PluginToTest, 'testdata/input', 'testdata/result', False) +``` + +## Help + +Run the following for an overview of all the available options in the test script: + +```bash +test_plugin --help +``` diff --git a/0.7.0/_sources/dev/spec.md.txt b/0.7.0/_sources/dev/spec.md.txt new file mode 100644 index 0000000..d7f18e4 --- /dev/null +++ b/0.7.0/_sources/dev/spec.md.txt @@ -0,0 +1,58 @@ +# Extraction Plugin specifications + +.. note:: If you use the Java or Python extraction plugin SDK, you don't have + to worry about these specifications. The Java and Python SDKs makes + sure your plugin is compiled and packaged conform to the extraction + plugin specifications. + +This page describes the specifications that define an extraction plugin. +The spec contains two major parts: a plugin protocol, and the plugin packaging +method. + +This specification applies to plugins that are not embedded within Hansken, +but to plugins that developed and distributed outside the scope of the Hansken +platform development. + +## Plugin protocol + +An extraction plugin is a process that implements a [GRPC](https://grpc.io/) +service `ExtractionPluginService`. The service defines a protocol that is +used to allow communication between Hansken and an extraction plugin. The +GRPC and protocol definitions can be found in the extraction plugin source +code, under the folder `grpc`. + +.. note:: The source code of the Extraction Plugin is currently not available + outside the scope of the Hansken core development teams. If you are + interested in the GRPC definitions, please :doc:`../contact` the + Hansken development team. + +## Packaging + +An extraction plugin is packaged as a container image -- conform the open +container initiative [image spec](https://github.com/opencontainers/image-spec). +An extraction plugin can be + +The `ENTRYPOINT` of the container image should be a process that starts a GRPC +server that implements the plugin protocol. The GRPC protocol should run on +port `8999`. + +The container image should be labeled with the plugin info. +The plugin info returned by the plugin plugin-info call and container labels +are required to match. If not, Hansken will not accept your plugin during +extractions - as it is unsure if the intended plugin is processing traces. + +The labels that are expected are: + +* `org.hansken.plugin-id` (conform :doc:`concepts/plugin_naming_convention`) +* `org.hansken.plugin-version` +* `org.hansken.plugin-api-version` +* `org.hansken.plugin-description` +* `org.hansken.plugin-webpage` +* `org.hansken.plugin-deferred-iterations` (optional, only has a meaning for deferred extraction plugins) +* `org.hansken.plugin-matcher` (see :doc:`concepts/hql_lite`) +* `org.hansken.plugin-license` +* `org.hansken.plugin-author-name` +* `org.hansken.plugin-author-organisation` +* `org.hansken.plugin-author-email` +* `org.hansken.plugin-resource-max_cpu` (in milicpu, optional) +* `org.hansken.plugin-resource-max_mem` (in mbs, optional) diff --git a/0.7.0/_sources/index.md.txt b/0.7.0/_sources/index.md.txt new file mode 100644 index 0000000..e675aa6 --- /dev/null +++ b/0.7.0/_sources/index.md.txt @@ -0,0 +1,52 @@ +Hansken extraction plugin SDK documentation for plugin developers +================================================================= + +.. image:: cartoon.png + :width: 500 + +## Quick links + +* [Latest version of this documentation](https://netherlandsforensicinstitute.github.io/hansken-extraction-plugin-sdk-documentation/latest) + +* Python: + [API Changelog](dev/python/api_changelog.md) | + [Code snippets](dev/python/snippets.md) | + [Python Examples](https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples/-/tree/master/python) + +* Java: + [API Changelog](dev/java/api_changelog.md) | + [Code snippets](dev/java/snippets.md) | + [Java Examples](https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples/-/tree/master/java) + +* [Extraction Plugins created by other community members](https://git.eminjenv.nl/hanskaton/extraction-plugins) +* Chat with us on [Discord](https://discord.com/channels/785509947416772649/796720708096360478) + +N.B. For access to any of the above external resources, please see ":ref:`communityaccess`" the in the [FAQ](dev/faq.md). + +## Welcome + +Welcome to the Hansken extraction plugin SDK documentation for plugin developers. +This documentation describes the Hansken extraction plugin Software Development Kit (SDK). +If you are new here, you can start by reading the [introduction](dev/introduction). + +.. ATTENTION:: + Hansken extraction plugins is a technology preview. + Please don't consider the SDK and integration in Hansken to be fully stable. + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + dev/introduction + dev/concepts + dev/spec + dev/java + dev/python + dev/examples + dev/faq + contact + changes + + +Cartoon by @jorgb, all rights reserved + diff --git a/0.7.0/_static/_sphinx_javascript_frameworks_compat.js b/0.7.0/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 0000000..8141580 --- /dev/null +++ b/0.7.0/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/0.7.0/_static/basic.css b/0.7.0/_static/basic.css new file mode 100644 index 0000000..7577acb --- /dev/null +++ b/0.7.0/_static/basic.css @@ -0,0 +1,903 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/0.7.0/_static/css/badge_only.css b/0.7.0/_static/css/badge_only.css new file mode 100644 index 0000000..c718cee --- /dev/null +++ b/0.7.0/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/0.7.0/_static/css/fonts/Roboto-Slab-Bold.woff b/0.7.0/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 0000000..6cb6000 Binary files /dev/null and b/0.7.0/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/0.7.0/_static/css/fonts/Roboto-Slab-Bold.woff2 b/0.7.0/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 0000000..7059e23 Binary files /dev/null and b/0.7.0/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/0.7.0/_static/css/fonts/Roboto-Slab-Regular.woff b/0.7.0/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 0000000..f815f63 Binary files /dev/null and b/0.7.0/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/0.7.0/_static/css/fonts/Roboto-Slab-Regular.woff2 b/0.7.0/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 0000000..f2c76e5 Binary files /dev/null and b/0.7.0/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/0.7.0/_static/css/fonts/fontawesome-webfont.eot b/0.7.0/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/0.7.0/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/0.7.0/_static/css/fonts/fontawesome-webfont.svg b/0.7.0/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/0.7.0/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/0.7.0/_static/css/fonts/fontawesome-webfont.ttf b/0.7.0/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/0.7.0/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/0.7.0/_static/css/fonts/fontawesome-webfont.woff b/0.7.0/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/0.7.0/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/0.7.0/_static/css/fonts/fontawesome-webfont.woff2 b/0.7.0/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/0.7.0/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/0.7.0/_static/css/fonts/lato-bold-italic.woff b/0.7.0/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 0000000..88ad05b Binary files /dev/null and b/0.7.0/_static/css/fonts/lato-bold-italic.woff differ diff --git a/0.7.0/_static/css/fonts/lato-bold-italic.woff2 b/0.7.0/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 0000000..c4e3d80 Binary files /dev/null and b/0.7.0/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/0.7.0/_static/css/fonts/lato-bold.woff b/0.7.0/_static/css/fonts/lato-bold.woff new file mode 100644 index 0000000..c6dff51 Binary files /dev/null and b/0.7.0/_static/css/fonts/lato-bold.woff differ diff --git a/0.7.0/_static/css/fonts/lato-bold.woff2 b/0.7.0/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 0000000..bb19504 Binary files /dev/null and b/0.7.0/_static/css/fonts/lato-bold.woff2 differ diff --git a/0.7.0/_static/css/fonts/lato-normal-italic.woff b/0.7.0/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 0000000..76114bc Binary files /dev/null and b/0.7.0/_static/css/fonts/lato-normal-italic.woff differ diff --git a/0.7.0/_static/css/fonts/lato-normal-italic.woff2 b/0.7.0/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 0000000..3404f37 Binary files /dev/null and b/0.7.0/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/0.7.0/_static/css/fonts/lato-normal.woff b/0.7.0/_static/css/fonts/lato-normal.woff new file mode 100644 index 0000000..ae1307f Binary files /dev/null and b/0.7.0/_static/css/fonts/lato-normal.woff differ diff --git a/0.7.0/_static/css/fonts/lato-normal.woff2 b/0.7.0/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 0000000..3bf9843 Binary files /dev/null and b/0.7.0/_static/css/fonts/lato-normal.woff2 differ diff --git a/0.7.0/_static/css/theme.css b/0.7.0/_static/css/theme.css new file mode 100644 index 0000000..19a446a --- /dev/null +++ b/0.7.0/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/0.7.0/_static/doctools.js b/0.7.0/_static/doctools.js new file mode 100644 index 0000000..d06a71d --- /dev/null +++ b/0.7.0/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/0.7.0/_static/documentation_options.js b/0.7.0/_static/documentation_options.js new file mode 100644 index 0000000..63bfaec --- /dev/null +++ b/0.7.0/_static/documentation_options.js @@ -0,0 +1,14 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '0.7.0', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/0.7.0/_static/file.png b/0.7.0/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/0.7.0/_static/file.png differ diff --git a/0.7.0/_static/javadoc/allclasses-index.html b/0.7.0/_static/javadoc/allclasses-index.html new file mode 100644 index 0000000..f557c8b --- /dev/null +++ b/0.7.0/_static/javadoc/allclasses-index.html @@ -0,0 +1,368 @@ + + + + + +All Classes (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

All Classes

+
+
+ +
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/allclasses.html b/0.7.0/_static/javadoc/allclasses.html new file mode 100644 index 0000000..82db6b8 --- /dev/null +++ b/0.7.0/_static/javadoc/allclasses.html @@ -0,0 +1,63 @@ + + + + + +All Classes (api 0.7.0 API) + + + + + + + + + + + + + +
+

All Classes

+ +
+ + diff --git a/0.7.0/_static/javadoc/allpackages-index.html b/0.7.0/_static/javadoc/allpackages-index.html new file mode 100644 index 0000000..9799181 --- /dev/null +++ b/0.7.0/_static/javadoc/allpackages-index.html @@ -0,0 +1,182 @@ + + + + + +All Packages (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

All Packages

+
+
+ +
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/constant-values.html b/0.7.0/_static/javadoc/constant-values.html new file mode 100644 index 0000000..6a3f182 --- /dev/null +++ b/0.7.0/_static/javadoc/constant-values.html @@ -0,0 +1,152 @@ + + + + + +Constant Field Values (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Constant Field Values

+
+

Contents

+
+
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/deprecated-list.html b/0.7.0/_static/javadoc/deprecated-list.html new file mode 100644 index 0000000..eadef67 --- /dev/null +++ b/0.7.0/_static/javadoc/deprecated-list.html @@ -0,0 +1,188 @@ + + + + + +Deprecated List (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Deprecated API

+

Contents

+ +
+ +
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/element-list b/0.7.0/_static/javadoc/element-list new file mode 100644 index 0000000..ef008a6 --- /dev/null +++ b/0.7.0/_static/javadoc/element-list @@ -0,0 +1,3 @@ +org.hansken.plugin.extraction.api +org.hansken.plugin.extraction.api.transformations +org.hansken.plugin.extraction.util diff --git a/0.7.0/_static/javadoc/help-doc.html b/0.7.0/_static/javadoc/help-doc.html new file mode 100644 index 0000000..2bc90df --- /dev/null +++ b/0.7.0/_static/javadoc/help-doc.html @@ -0,0 +1,282 @@ + + + + + +API Help (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

How This API Document Is Organized

+
This API (Application Programming Interface) document has pages corresponding to the items in the navigation bar, described as follows.
+
+
+
    +
  • +
    +

    Overview

    +

    The Overview page is the front page of this API document and provides a list of all packages with a summary for each. This page can also contain an overall description of the set of packages.

    +
    +
  • +
  • +
    +

    Package

    +

    Each package has a page that contains a list of its classes and interfaces, with a summary for each. These pages may contain six categories:

    +
      +
    • Interfaces
    • +
    • Classes
    • +
    • Enums
    • +
    • Exceptions
    • +
    • Errors
    • +
    • Annotation Types
    • +
    +
    +
  • +
  • +
    +

    Class or Interface

    +

    Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a class/interface description, summary tables, and detailed member descriptions:

    +
      +
    • Class Inheritance Diagram
    • +
    • Direct Subclasses
    • +
    • All Known Subinterfaces
    • +
    • All Known Implementing Classes
    • +
    • Class or Interface Declaration
    • +
    • Class or Interface Description
    • +
    +
    +
      +
    • Nested Class Summary
    • +
    • Field Summary
    • +
    • Property Summary
    • +
    • Constructor Summary
    • +
    • Method Summary
    • +
    +
    +
      +
    • Field Detail
    • +
    • Property Detail
    • +
    • Constructor Detail
    • +
    • Method Detail
    • +
    +

    Each summary entry contains the first sentence from the detailed description for that item. The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.

    +
    +
  • +
  • +
    +

    Annotation Type

    +

    Each annotation type has its own separate page with the following sections:

    +
      +
    • Annotation Type Declaration
    • +
    • Annotation Type Description
    • +
    • Required Element Summary
    • +
    • Optional Element Summary
    • +
    • Element Detail
    • +
    +
    +
  • +
  • +
    +

    Enum

    +

    Each enum has its own separate page with the following sections:

    +
      +
    • Enum Declaration
    • +
    • Enum Description
    • +
    • Enum Constant Summary
    • +
    • Enum Constant Detail
    • +
    +
    +
  • +
  • +
    +

    Use

    +

    Each documented package, class and interface has its own Use page. This page describes what packages, classes, methods, constructors and fields use any part of the given class or package. Given a class or interface A, its "Use" page includes subclasses of A, fields declared as A, methods that return A, and methods and constructors with parameters of type A. You can access this page by first going to the package, class or interface, then clicking on the "Use" link in the navigation bar.

    +
    +
  • +
  • +
    +

    Tree (Class Hierarchy)

    +

    There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. Classes are organized by inheritance structure starting with java.lang.Object. Interfaces do not inherit from java.lang.Object.

    +
      +
    • When viewing the Overview page, clicking on "Tree" displays the hierarchy for all packages.
    • +
    • When viewing a particular package, class or interface page, clicking on "Tree" displays the hierarchy for only that package.
    • +
    +
    +
  • +
  • +
    +

    Deprecated API

    +

    The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to improvements, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.

    +
    +
  • +
  • +
    +

    Index

    +

    The Index contains an alphabetic index of all classes, interfaces, constructors, methods, and fields, as well as lists of all packages and all classes.

    +
    +
  • +
  • +
    +

    All Classes

    +

    The All Classes link shows all classes and interfaces except non-static nested types.

    +
    +
  • +
  • +
    +

    Serialized Form

    +

    Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to re-implementors, not to developers using the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See also" section of the class description.

    +
    +
  • +
  • +
    +

    Constant Field Values

    +

    The Constant Field Values page lists the static final fields and their values.

    +
    +
  • +
  • +
    +

    Search

    +

    You can search for definitions of modules, packages, types, fields, methods and other terms defined in the API, using some or all of the name. "Camel-case" abbreviations are supported: for example, "InpStr" will find "InputStream" and "InputStreamReader".

    +
    +
  • +
+
+This help file applies to API documentation generated by the standard doclet.
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/index-all.html b/0.7.0/_static/javadoc/index-all.html new file mode 100644 index 0000000..ce29217 --- /dev/null +++ b/0.7.0/_static/javadoc/index-all.html @@ -0,0 +1,931 @@ + + + + + +Index (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
A B C D E F G H I L M N O P R S T V W 
All Classes All Packages + + +

A

+
+
accept(T) - Method in interface org.hansken.plugin.extraction.util.ThrowingConsumer
+
+
Performs this operation on the given argument.
+
+
add(List<DataRange>) - Method in class org.hansken.plugin.extraction.api.transformations.RangedDataTransformation.Builder
+
 
+
add(DataRange...) - Method in class org.hansken.plugin.extraction.api.transformations.RangedDataTransformation.Builder
+
 
+
addRange(long, long) - Method in class org.hansken.plugin.extraction.api.transformations.RangedDataTransformation.Builder
+
 
+
addTracelet(String, Consumer<Trace.TraceletBuilder>) - Method in interface org.hansken.plugin.extraction.api.Trace
+
+
Add a tracelet to the trace.
+
+
addTracelet(Trace.Tracelet) - Method in interface org.hansken.plugin.extraction.api.Trace
+
+
Deprecated. +
use addTracelet(type, callback)
+
+
+
addType(String) - Method in interface org.hansken.plugin.extraction.api.Trace
+
+
Add a type to this trace (for example: "file").
+
+
ArgChecks - Class in org.hansken.plugin.extraction.util
+
+
A collection of methods for defensively checking arguments passed to methods.
+
+
argNotAllNull(String, Object...) - Static method in class org.hansken.plugin.extraction.util.ArgChecks
+
+
Check that not all values with given name are null, otherwise throw an exception.
+
+
argNotEmpty(String, String) - Static method in class org.hansken.plugin.extraction.util.ArgChecks
+
+
Check that the string with given name is not null or empty, otherwise throw an exception.
+
+
argNotEmpty(String, Collection<T>) - Static method in class org.hansken.plugin.extraction.util.ArgChecks
+
+
Check that the collection value with given name is not empty, otherwise throw an exception.
+
+
argNotEmpty(String, T[]) - Static method in class org.hansken.plugin.extraction.util.ArgChecks
+
+
Check that the array value with given name is not empty, otherwise throw an exception.
+
+
argNotNegative(String, float) - Static method in class org.hansken.plugin.extraction.util.ArgChecks
+
+
Check that the float with given name is not negative, otherwise throw an exception.
+
+
argNotNegative(String, int) - Static method in class org.hansken.plugin.extraction.util.ArgChecks
+
+
Check that the int with given name is not negative, otherwise throw an exception.
+
+
argNotNegative(String, long) - Static method in class org.hansken.plugin.extraction.util.ArgChecks
+
+
Check that the long with given name is not negative, otherwise throw an exception.
+
+
argNotNull(String, T) - Static method in class org.hansken.plugin.extraction.util.ArgChecks
+
+
Check that the value with given name is not null, otherwise throw an exception.
+
+
argsIsType(String, List<T>, Class<?>) - Static method in class org.hansken.plugin.extraction.util.ArgChecks
+
+
Check that the value with given name is of type type, otherwise throw an exception.
+
+
asBinary() - Method in class org.hansken.plugin.extraction.api.Vector
+
+
Returns the binary representation of the Vector.
+
+
asVector(byte[]) - Static method in class org.hansken.plugin.extraction.api.Vector
+
+
Creates a vector from a binary representation.
+
+
author() - Method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Get the author of this plugin.
+
+
author(Author) - Method in class org.hansken.plugin.extraction.api.PluginInfo.Builder
+
+
Set the author.
+
+
Author - Class in org.hansken.plugin.extraction.api
+
+
An author of a certain extraction plugin.
+
+
Author.Builder - Class in org.hansken.plugin.extraction.api
+
+
A builder for an author.
+
+
+ + + +

B

+
+
BaseExtractionPlugin - Interface in org.hansken.plugin.extraction.api
+
+
This the base class for types of Extraction Plugins, and cannot be used solely as a superclass for a plugin.
+
+
BatchSearchResult - Class in org.hansken.plugin.extraction.api
+
+
A BatchSearchResult is a SearchResult implementation that stores all found traces using a single setTraces call.
+
+
BatchSearchResult(long) - Constructor for class org.hansken.plugin.extraction.api.BatchSearchResult
+
 
+
build() - Method in class org.hansken.plugin.extraction.api.Author.Builder
+
+
Create the author from the properties set on this builder.
+
+
build() - Method in class org.hansken.plugin.extraction.api.PluginInfo.Builder
+
+
Create the plugin information from the properties set on this builder.
+
+
build() - Method in class org.hansken.plugin.extraction.api.PluginResources.Builder
+
 
+
build() - Method in class org.hansken.plugin.extraction.api.transformations.RangedDataTransformation.Builder
+
 
+
builder() - Static method in class org.hansken.plugin.extraction.api.Author
+
+
Start creating a new author.
+
+
builder() - Static method in class org.hansken.plugin.extraction.api.PluginResources
+
 
+
builder() - Static method in class org.hansken.plugin.extraction.api.transformations.RangedDataTransformation
+
+ +
+
Builder() - Constructor for class org.hansken.plugin.extraction.api.Author.Builder
+
 
+
Builder() - Constructor for class org.hansken.plugin.extraction.api.transformations.RangedDataTransformation.Builder
+
 
+
builderFor(BaseExtractionPlugin) - Static method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Start creating new plugin information for the given plugin.
+
+
builderFor(PluginType) - Static method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Start creating new plugin information for a plugin of given type.
+
+
+ + + +

C

+
+
category() - Method in class org.hansken.plugin.extraction.api.PluginId
+
 
+
+ + + +

D

+
+
data() - Method in interface org.hansken.plugin.extraction.api.DataContext
+
+
A data sequence belonging to the trace currently being extracted.
+
+
DataContext - Interface in org.hansken.plugin.extraction.api
+
+
The data context contains information about a data stream of a trace that is currently being + processed by an ExtractionPlugin or DeferredExtractionPlugin.
+
+
DataRange - Class in org.hansken.plugin.extraction.api.transformations
+
+
A DataRange describes a range of bytes with an offset and length.
+
+
DataRange(long, long) - Constructor for class org.hansken.plugin.extraction.api.transformations.DataRange
+
+
Creates a DataRange which describes a range of bytes.
+
+
DataTransformation - Interface in org.hansken.plugin.extraction.api.transformations
+
+
A data transformation that the Extraction Plugin can write to a Trace using + Trace.setData(String, DataTransformation...).
+
+
dataType() - Method in interface org.hansken.plugin.extraction.api.DataContext
+
+
The type of data (see DataContext.data()) that is being processed.
+
+
DataWriter - Interface in org.hansken.plugin.extraction.api
+
+
Writes data to an OutputStream.
+
+
DEFERRED_EXTRACTION_PLUGIN - org.hansken.plugin.extraction.api.PluginType
+
+ +
+
DeferredExtractionPlugin - Interface in org.hansken.plugin.extraction.api
+
+
Deferred extraction plugins can be used by Hansken to process traces during the extraction process.
+
+
deferredIterations() - Method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Get the number of extraction iterations before the deferred plugin can be applied on traces.
+
+
deferredIterations(int) - Method in class org.hansken.plugin.extraction.api.PluginInfo.Builder
+
+
Set the number of extraction iterations needed for this deferred plugin.
+
+
description() - Method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Get a human readable description of this plugin.
+
+
description(String) - Method in class org.hansken.plugin.extraction.api.PluginInfo.Builder
+
+
Set the description.
+
+
domain() - Method in class org.hansken.plugin.extraction.api.PluginId
+
 
+
+ + + +

E

+
+
email() - Method in class org.hansken.plugin.extraction.api.Author
+
+
Get the email address of the author.
+
+
email(String) - Method in class org.hansken.plugin.extraction.api.Author.Builder
+
+
Set the email.
+
+
equals(Object) - Method in class org.hansken.plugin.extraction.api.LatLong
+
 
+
equals(Object) - Method in class org.hansken.plugin.extraction.api.Vector
+
 
+
EXTRACTION_PLUGIN - org.hansken.plugin.extraction.api.PluginType
+
+ +
+
ExtractionPlugin - Interface in org.hansken.plugin.extraction.api
+
+
Extraction plugins can be used by Hansken to process traces during the extraction process.
+
+
+ + + +

F

+
+
fullName() - Method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Get the full name of this plugin, based on the id.
+
+
+ + + +

G

+
+
get(String) - Method in interface org.hansken.plugin.extraction.api.ImmutableTrace
+
+
Get the value of the property with given name on this trace.
+
+
getData(String) - Method in interface org.hansken.plugin.extraction.api.SearchTrace
+
+
Returns a RandomAccessData for a specific trace data type.
+
+
getDataTypes() - Method in interface org.hansken.plugin.extraction.api.SearchTrace
+
+
Returns all available data types for this search trace.
+
+
getLength() - Method in class org.hansken.plugin.extraction.api.transformations.DataRange
+
 
+
getName() - Method in class org.hansken.plugin.extraction.api.Trace.Tracelet
+
 
+
getName() - Method in class org.hansken.plugin.extraction.api.Trace.TraceletProperty
+
 
+
getOffset() - Method in class org.hansken.plugin.extraction.api.transformations.DataRange
+
 
+
getRanges() - Method in class org.hansken.plugin.extraction.api.transformations.RangedDataTransformation
+
 
+
getTotalHits() - Method in class org.hansken.plugin.extraction.api.BatchSearchResult
+
 
+
getTotalHits() - Method in interface org.hansken.plugin.extraction.api.SearchResult
+
+
Returns the total number of traces matching the query.
+
+
getTraces() - Method in class org.hansken.plugin.extraction.api.BatchSearchResult
+
 
+
getTraces() - Method in interface org.hansken.plugin.extraction.api.SearchResult
+
+
Returns all found traces.
+
+
getValue() - Method in class org.hansken.plugin.extraction.api.Trace.Tracelet
+
 
+
getValue() - Method in class org.hansken.plugin.extraction.api.Trace.TraceletProperty
+
 
+
+ + + +

H

+
+
hashCode() - Method in class org.hansken.plugin.extraction.api.LatLong
+
 
+
hashCode() - Method in class org.hansken.plugin.extraction.api.Vector
+
 
+
hqlMatcher() - Method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Get the hqlMatcher of this plugin in string format.
+
+
hqlMatcher(String) - Method in class org.hansken.plugin.extraction.api.PluginInfo.Builder
+
+
Set the hqlMatcher query in string format.
+
+
+ + + +

I

+
+
id() - Method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Get the the unique id of this plugin, consisting of domain, category and name.
+
+
id(String, String, String) - Method in class org.hansken.plugin.extraction.api.PluginInfo.Builder
+
+
Set the unique id of this plugin, consisting of domain, category and name.
+
+
id(PluginId) - Method in class org.hansken.plugin.extraction.api.PluginInfo.Builder
+
+
Set the unique id of this plugin, consisting of domain, category and name.
+
+
ImmutableTrace - Interface in org.hansken.plugin.extraction.api
+
+
A trace contains information about processed data.
+
+
+ + + +

L

+
+
latitude() - Method in class org.hansken.plugin.extraction.api.LatLong
+
+
The north-south position of a point on earth.
+
+
LatLong - Class in org.hansken.plugin.extraction.api
+
+
A geographical location consisting of a latitude and a longitude.
+
+
LatLong(double, double) - Constructor for class org.hansken.plugin.extraction.api.LatLong
+
 
+
license() - Method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Get the name of the license of this plugin.
+
+
license(String) - Method in class org.hansken.plugin.extraction.api.PluginInfo.Builder
+
+
Set the name of the license of this plugin.
+
+
longitude() - Method in class org.hansken.plugin.extraction.api.LatLong
+
+
The east-west position of a point on earth.
+
+
+ + + +

M

+
+
maturityLevel() - Method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Get the maturity level of this plugin.
+
+
maturityLevel(MaturityLevel) - Method in class org.hansken.plugin.extraction.api.PluginInfo.Builder
+
+ +
+
MaturityLevel - Enum in org.hansken.plugin.extraction.api
+
+
Indicates what stage of maturity a certain extraction plugin is in.
+
+
maximumCpu() - Method in class org.hansken.plugin.extraction.api.PluginResources
+
 
+
maximumCpu(float) - Method in class org.hansken.plugin.extraction.api.PluginResources.Builder
+
 
+
maximumMemory() - Method in class org.hansken.plugin.extraction.api.PluginResources
+
 
+
maximumMemory(int) - Method in class org.hansken.plugin.extraction.api.PluginResources.Builder
+
 
+
META_EXTRACTION_PLUGIN - org.hansken.plugin.extraction.api.PluginType
+
+ +
+
MetaExtractionPlugin - Class in org.hansken.plugin.extraction.api
+
+
Meta extraction plugins can be used by Hansken to process traces during the extraction process.
+
+
MetaExtractionPlugin() - Constructor for class org.hansken.plugin.extraction.api.MetaExtractionPlugin
+
 
+
+ + + +

N

+
+
name() - Method in class org.hansken.plugin.extraction.api.Author
+
+
Get the name of the author.
+
+
name() - Method in class org.hansken.plugin.extraction.api.PluginId
+
 
+
name() - Method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Deprecated. +
Use PluginInfo.id() instead.
+
+
+
name(String) - Method in class org.hansken.plugin.extraction.api.Author.Builder
+
+
Set the name.
+
+
name(String) - Method in class org.hansken.plugin.extraction.api.PluginInfo.Builder
+
+
Deprecated. + +
+
+
newChild(String, ThrowingConsumer<Trace, IOException>) - Method in interface org.hansken.plugin.extraction.api.Trace
+
+
Create and store new child trace of this trace.
+
+
+ + + +

O

+
+
of(double, double) - Static method in class org.hansken.plugin.extraction.api.LatLong
+
+
Create a new geographical point with given latitude + and longitude.
+
+
of(float...) - Static method in class org.hansken.plugin.extraction.api.Vector
+
+
Creates a Vector from an array of floating point values.
+
+
of(Collection<Float>) - Static method in class org.hansken.plugin.extraction.api.Vector
+
+
Creates a Vector from a collection of numbers.
+
+
ofBase64(String) - Static method in class org.hansken.plugin.extraction.api.Vector
+
+
Creates a Vector from a base64 encoded string.
+
+
org.hansken.plugin.extraction.api - package org.hansken.plugin.extraction.api
+
+
This is the API of the Extraction Plugins SDK.
+
+
org.hansken.plugin.extraction.api.transformations - package org.hansken.plugin.extraction.api.transformations
+
+
This package contains the Data Transformations.
+
+
org.hansken.plugin.extraction.util - package org.hansken.plugin.extraction.util
+
+
This package provides util classes for the Extraction Plugin SDK API.
+
+
organisation() - Method in class org.hansken.plugin.extraction.api.Author
+
+
Get the name of the organisation the author belongs to.
+
+
organisation(String) - Method in class org.hansken.plugin.extraction.api.Author.Builder
+
+
Set the organisation.
+
+
+ + + +

P

+
+
PluginId - Class in org.hansken.plugin.extraction.api
+
+
Identifier of a plugin, consisting of domain, category and name.
+
+
PluginId(String, String, String) - Constructor for class org.hansken.plugin.extraction.api.PluginId
+
+
Create a unique identifier for a plugin, consisting of domain, category and name.
+
+
pluginInfo() - Method in interface org.hansken.plugin.extraction.api.BaseExtractionPlugin
+
+
Get the information of this plugin, such as the author or a description, and the + types of traces and data it matches on.
+
+
PluginInfo - Class in org.hansken.plugin.extraction.api
+
+
Information about an extraction plugin, such as the author or + a human-readable description.
+
+
PluginInfo.Builder - Class in org.hansken.plugin.extraction.api
+
+
A builder for plugin information.
+
+
PluginResources - Class in org.hansken.plugin.extraction.api
+
+
PluginResources contains information about how many resources will be used for a plugin.
+
+
PluginResources.Builder - Class in org.hansken.plugin.extraction.api
+
+
Builder for PluginResources.
+
+
pluginType() - Method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Get the type of this plugin.
+
+
PluginType - Enum in org.hansken.plugin.extraction.api
+
+
The type of plugin, which describes its function.
+
+
pluginVersion() - Method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Get the version of this plugin.
+
+
pluginVersion(String) - Method in class org.hansken.plugin.extraction.api.PluginInfo.Builder
+
+ +
+
position() - Method in interface org.hansken.plugin.extraction.api.RandomAccessData
+
+
Get the position in the sequence.
+
+
process(Trace) - Method in class org.hansken.plugin.extraction.api.MetaExtractionPlugin
+
+
Start processing a trace without any of its associated data streams.
+
+
process(Trace, DataContext) - Method in interface org.hansken.plugin.extraction.api.ExtractionPlugin
+
+
Start processing a trace with a given data context.
+
+
process(Trace, DataContext) - Method in class org.hansken.plugin.extraction.api.MetaExtractionPlugin
+
 
+
process(Trace, DataContext, TraceSearcher) - Method in interface org.hansken.plugin.extraction.api.DeferredExtractionPlugin
+
+
Start processing a trace.
+
+
PRODUCTION_READY - org.hansken.plugin.extraction.api.MaturityLevel
+
+
The plugin is ready to be used in a production environment.
+
+
PROOF_OF_CONCEPT - org.hansken.plugin.extraction.api.MaturityLevel
+
+
The plugin is in a proof of concept phase, not yet ready for test or production.
+
+
properties() - Method in interface org.hansken.plugin.extraction.api.ImmutableTrace
+
+
Return the names of all the properties contained in this trace.
+
+
+ + + +

R

+
+
RandomAccessData - Interface in org.hansken.plugin.extraction.api
+
+
A random access readable byte sequence.
+
+
RangedDataTransformation - Class in org.hansken.plugin.extraction.api.transformations
+
+
A RangedDataTransformation describes a data transformation consisting of a list of DataRanges.
+
+
RangedDataTransformation(List<DataRange>) - Constructor for class org.hansken.plugin.extraction.api.transformations.RangedDataTransformation
+
+
Create a new RangedDataTransformation given a list of DataRanges.
+
+
RangedDataTransformation(DataRange...) - Constructor for class org.hansken.plugin.extraction.api.transformations.RangedDataTransformation
+
+
Create a new RangedDataTransformation given one or more DataRanges.
+
+
RangedDataTransformation.Builder - Class in org.hansken.plugin.extraction.api.transformations
+
+
Builder for creating RangedDataTransformation using a syntax that is sometimes shorter than using + constructors.
+
+
read(byte[]) - Method in interface org.hansken.plugin.extraction.api.RandomAccessData
+
+
Read bytes into the given buffer, starting at position 0 in the buffer.
+
+
read(byte[], int) - Method in interface org.hansken.plugin.extraction.api.RandomAccessData
+
+
Read bytes into the given buffer, starting at position 0 in the buffer.
+
+
read(byte[], int, int) - Method in interface org.hansken.plugin.extraction.api.RandomAccessData
+
+
Read data into the given buffer, starting at position offset in the buffer.
+
+
readNBytes(int) - Method in interface org.hansken.plugin.extraction.api.RandomAccessData
+
+
Read from the data sequence, returning the read bytes as an array.The data will be read from the current + position and the amount of bytes read will equal count, unless the sequence contains + fewer remaining bytes.
+
+
READY_FOR_TEST - org.hansken.plugin.extraction.api.MaturityLevel
+
+
The plugin can be used in a test environment and is expected to be fully functional.
+
+
remaining() - Method in interface org.hansken.plugin.extraction.api.RandomAccessData
+
+
Get the number of remaining bytes in this data sequence.
+
+
resources() - Method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Get the resources of this plugin.
+
+
resources(PluginResources) - Method in class org.hansken.plugin.extraction.api.PluginInfo.Builder
+
+
Set the resources of this plugin (optional).
+
+
+ + + +

S

+
+
search(String, int) - Method in interface org.hansken.plugin.extraction.api.TraceSearcher
+
+
Searches in Hansken for Traces matching provided query.
+
+
SearchResult - Interface in org.hansken.plugin.extraction.api
+
+
A SearchResult represents the result of a TraceSearcher.search(String, int).
+
+
SearchTrace - Interface in org.hansken.plugin.extraction.api
+
+
A trace contains information about processed data.
+
+
seek(long) - Method in interface org.hansken.plugin.extraction.api.RandomAccessData
+
+
Move to the given absolute position in the data sequence.
+
+
set(String, Object) - Method in interface org.hansken.plugin.extraction.api.Trace
+
+
Set a property on this trace with a given value.
+
+
set(String, Object) - Method in interface org.hansken.plugin.extraction.api.Trace.TraceletBuilder
+
+
Set a property on this tracelet with a given value.
+
+
setData(String, InputStream) - Method in interface org.hansken.plugin.extraction.api.Trace
+
+
Add a data stream of a given type to this Trace (for example, 'raw' or 'html').
+
+
setData(String, List<DataTransformation>) - Method in interface org.hansken.plugin.extraction.api.Trace
+
+
Set a series of data transformations for a specific dataType.
+
+
setData(String, DataWriter) - Method in interface org.hansken.plugin.extraction.api.Trace
+
+
Add a data stream of a given type to this Trace (for example, 'raw' or 'html').
+
+
setData(String, DataTransformation...) - Method in interface org.hansken.plugin.extraction.api.Trace
+
+
Set a series of data transformations for a specific dataType.
+
+
setLength(long) - Method in class org.hansken.plugin.extraction.api.transformations.DataRange
+
 
+
setOffset(long) - Method in class org.hansken.plugin.extraction.api.transformations.DataRange
+
 
+
setRanges(List<DataRange>) - Method in class org.hansken.plugin.extraction.api.transformations.RangedDataTransformation
+
 
+
setTraces(SearchTrace[]) - Method in class org.hansken.plugin.extraction.api.BatchSearchResult
+
+
Sets all traces that can be returned by calling getTraces.
+
+
size() - Method in interface org.hansken.plugin.extraction.api.RandomAccessData
+
+
Get the number of bytes contained in this data sequence.
+
+
size() - Method in class org.hansken.plugin.extraction.api.Vector
+
+
Returns the number of dimensions of the vector.
+
+
+ + + +

T

+
+
ThrowingConsumer<T,​E extends Throwable> - Interface in org.hansken.plugin.extraction.util
+
+
Represents an operation that accepts a single input argument and returns no result.
+
+
toBase64() - Method in class org.hansken.plugin.extraction.api.Vector
+
+
Returns a base64 encoded string of the Vector.
+
+
toISO6709() - Method in class org.hansken.plugin.extraction.api.LatLong
+
+
Format to String using ISO 6709.
+
+
toString() - Method in class org.hansken.plugin.extraction.api.LatLong
+
 
+
toString() - Method in class org.hansken.plugin.extraction.api.PluginId
+
 
+
toString() - Method in class org.hansken.plugin.extraction.api.Vector
+
 
+
Trace - Interface in org.hansken.plugin.extraction.api
+
+
A trace contains information about processed data.
+
+
Trace.Tracelet - Class in org.hansken.plugin.extraction.api
+
+
a Tracelet represents tracedata that can be present multiple times within a trace.
+
+
Trace.TraceletBuilder - Interface in org.hansken.plugin.extraction.api
+
 
+
Trace.TraceletProperty - Class in org.hansken.plugin.extraction.api
+
+
a TraceletProperty is a property of a Tracelet.
+
+
traceId() - Method in interface org.hansken.plugin.extraction.api.ImmutableTrace
+
+
Get the trace id of this trace.
+
+
Tracelet(String, List<Trace.TraceletProperty>) - Constructor for class org.hansken.plugin.extraction.api.Trace.Tracelet
+
 
+
TraceletProperty(String, Object) - Constructor for class org.hansken.plugin.extraction.api.Trace.TraceletProperty
+
 
+
TraceSearcher - Interface in org.hansken.plugin.extraction.api
+
+
Allows searching for traces within the scope of the process function.
+
+
types() - Method in interface org.hansken.plugin.extraction.api.ImmutableTrace
+
+
Get all the types of this trace.
+
+
+ + + +

V

+
+
valueOf(String) - Static method in enum org.hansken.plugin.extraction.api.MaturityLevel
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum org.hansken.plugin.extraction.api.PluginType
+
+
Returns the enum constant of this type with the specified name.
+
+
values() - Static method in enum org.hansken.plugin.extraction.api.MaturityLevel
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum org.hansken.plugin.extraction.api.PluginType
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Method in class org.hansken.plugin.extraction.api.Vector
+
+
Returns the values of the Vector as an array of floats.
+
+
Vector - Class in org.hansken.plugin.extraction.api
+
+
An opaque vector of floating point values.
+
+
+ + + +

W

+
+
webpageUrl() - Method in class org.hansken.plugin.extraction.api.PluginInfo
+
+
Get the url of this plugin, could point to git repo or webpage that explains the plugin.
+
+
webpageUrl(String) - Method in class org.hansken.plugin.extraction.api.PluginInfo.Builder
+
+
Set the url to a webpage that belongs to this plugin.
+
+
writeTo(OutputStream) - Method in interface org.hansken.plugin.extraction.api.DataWriter
+
+
Write data to given OutputStream.
+
+
+A B C D E F G H I L M N O P R S T V W 
All Classes All Packages
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/index.html b/0.7.0/_static/javadoc/index.html new file mode 100644 index 0000000..3bdebda --- /dev/null +++ b/0.7.0/_static/javadoc/index.html @@ -0,0 +1,178 @@ + + + + + +Overview (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

api 0.7.0 API

+
+
+ + + + + + + + + + + + + + + + + + + + +
Packages 
PackageDescription
org.hansken.plugin.extraction.api +
This is the API of the Extraction Plugins SDK.
+
org.hansken.plugin.extraction.api.transformations +
This package contains the Data Transformations.
+
org.hansken.plugin.extraction.util +
This package provides util classes for the Extraction Plugin SDK API.
+
+
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/jquery-ui.overrides.css b/0.7.0/_static/javadoc/jquery-ui.overrides.css new file mode 100644 index 0000000..facf852 --- /dev/null +++ b/0.7.0/_static/javadoc/jquery-ui.overrides.css @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active, +a.ui-button:active, +.ui-button:active, +.ui-button.ui-state-active:hover { + /* Overrides the color of selection used in jQuery UI */ + background: #F8981D; + border: 1px solid #F8981D; +} diff --git a/0.7.0/_static/javadoc/jquery/external/jquery/jquery.js b/0.7.0/_static/javadoc/jquery/external/jquery/jquery.js new file mode 100644 index 0000000..5093733 --- /dev/null +++ b/0.7.0/_static/javadoc/jquery/external/jquery/jquery.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( "\r\n"; + +// inject VBScript +document.write(IEBinaryToArray_ByteStr_Script); + +global.JSZipUtils._getBinaryFromXHR = function (xhr) { + var binary = xhr.responseBody; + var byteMapping = {}; + for ( var i = 0; i < 256; i++ ) { + for ( var j = 0; j < 256; j++ ) { + byteMapping[ String.fromCharCode( i + (j << 8) ) ] = + String.fromCharCode(i) + String.fromCharCode(j); + } + } + var rawBytes = IEBinaryToArray_ByteStr(binary); + var lastChr = IEBinaryToArray_ByteStr_Last(binary); + return rawBytes.replace(/[\s\S]/g, function( match ) { + return byteMapping[match]; + }) + lastChr; +}; + +// enforcing Stuk's coding style +// vim: set shiftwidth=4 softtabstop=4: + +},{}]},{},[1]) +; diff --git a/0.7.0/_static/javadoc/jquery/jszip-utils/dist/jszip-utils-ie.min.js b/0.7.0/_static/javadoc/jquery/jszip-utils/dist/jszip-utils-ie.min.js new file mode 100644 index 0000000..93d8bc8 --- /dev/null +++ b/0.7.0/_static/javadoc/jquery/jszip-utils/dist/jszip-utils-ie.min.js @@ -0,0 +1,10 @@ +/*! + +JSZipUtils - A collection of cross-browser utilities to go along with JSZip. + + +(c) 2014 Stuart Knightley, David Duponchel +Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip-utils/master/LICENSE.markdown. + +*/ +!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g\r\n";document.write(b),a.JSZipUtils._getBinaryFromXHR=function(a){for(var b=a.responseBody,c={},d=0;256>d;d++)for(var e=0;256>e;e++)c[String.fromCharCode(d+(e<<8))]=String.fromCharCode(d)+String.fromCharCode(e);var f=IEBinaryToArray_ByteStr(b),g=IEBinaryToArray_ByteStr_Last(b);return f.replace(/[\s\S]/g,function(a){return c[a]})+g}},{}]},{},[1]); diff --git a/0.7.0/_static/javadoc/jquery/jszip-utils/dist/jszip-utils.js b/0.7.0/_static/javadoc/jquery/jszip-utils/dist/jszip-utils.js new file mode 100644 index 0000000..775895e --- /dev/null +++ b/0.7.0/_static/javadoc/jquery/jszip-utils/dist/jszip-utils.js @@ -0,0 +1,118 @@ +/*! + +JSZipUtils - A collection of cross-browser utilities to go along with JSZip. + + +(c) 2014 Stuart Knightley, David Duponchel +Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip-utils/master/LICENSE.markdown. + +*/ +!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.JSZipUtils=e():"undefined"!=typeof global?global.JSZipUtils=e():"undefined"!=typeof self&&(self.JSZipUtils=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o + +(c) 2014 Stuart Knightley, David Duponchel +Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip-utils/master/LICENSE.markdown. + +*/ +!function(a){"object"==typeof exports?module.exports=a():"function"==typeof define&&define.amd?define(a):"undefined"!=typeof window?window.JSZipUtils=a():"undefined"!=typeof global?global.JSZipUtils=a():"undefined"!=typeof self&&(self.JSZipUtils=a())}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g + +(c) 2009-2016 Stuart Knightley +Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown. + +JSZip uses the library pako released under the MIT license : +https://github.com/nodeca/pako/blob/master/LICENSE +*/ + +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.JSZip = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = remainingBytes > 1 ? (((chr2 & 15) << 2) | (chr3 >> 6)) : 64; + enc4 = remainingBytes > 2 ? (chr3 & 63) : 64; + + output.push(_keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4)); + + } + + return output.join(""); +}; + +// public method for decoding +exports.decode = function(input) { + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0, resultIndex = 0; + + var dataUrlPrefix = "data:"; + + if (input.substr(0, dataUrlPrefix.length) === dataUrlPrefix) { + // This is a common error: people give a data url + // (data:image/png;base64,iVBOR...) with a {base64: true} and + // wonders why things don't work. + // We can detect that the string input looks like a data url but we + // *can't* be sure it is one: removing everything up to the comma would + // be too dangerous. + throw new Error("Invalid base64 input, it looks like a data url."); + } + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + var totalLength = input.length * 3 / 4; + if(input.charAt(input.length - 1) === _keyStr.charAt(64)) { + totalLength--; + } + if(input.charAt(input.length - 2) === _keyStr.charAt(64)) { + totalLength--; + } + if (totalLength % 1 !== 0) { + // totalLength is not an integer, the length does not match a valid + // base64 content. That can happen if: + // - the input is not a base64 content + // - the input is *almost* a base64 content, with a extra chars at the + // beginning or at the end + // - the input uses a base64 variant (base64url for example) + throw new Error("Invalid base64 input, bad content length."); + } + var output; + if (support.uint8array) { + output = new Uint8Array(totalLength|0); + } else { + output = new Array(totalLength|0); + } + + while (i < input.length) { + + enc1 = _keyStr.indexOf(input.charAt(i++)); + enc2 = _keyStr.indexOf(input.charAt(i++)); + enc3 = _keyStr.indexOf(input.charAt(i++)); + enc4 = _keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output[resultIndex++] = chr1; + + if (enc3 !== 64) { + output[resultIndex++] = chr2; + } + if (enc4 !== 64) { + output[resultIndex++] = chr3; + } + + } + + return output; +}; + +},{"./support":30,"./utils":32}],2:[function(require,module,exports){ +'use strict'; + +var external = require("./external"); +var DataWorker = require('./stream/DataWorker'); +var Crc32Probe = require('./stream/Crc32Probe'); +var DataLengthProbe = require('./stream/DataLengthProbe'); + +/** + * Represent a compressed object, with everything needed to decompress it. + * @constructor + * @param {number} compressedSize the size of the data compressed. + * @param {number} uncompressedSize the size of the data after decompression. + * @param {number} crc32 the crc32 of the decompressed file. + * @param {object} compression the type of compression, see lib/compressions.js. + * @param {String|ArrayBuffer|Uint8Array|Buffer} data the compressed data. + */ +function CompressedObject(compressedSize, uncompressedSize, crc32, compression, data) { + this.compressedSize = compressedSize; + this.uncompressedSize = uncompressedSize; + this.crc32 = crc32; + this.compression = compression; + this.compressedContent = data; +} + +CompressedObject.prototype = { + /** + * Create a worker to get the uncompressed content. + * @return {GenericWorker} the worker. + */ + getContentWorker: function () { + var worker = new DataWorker(external.Promise.resolve(this.compressedContent)) + .pipe(this.compression.uncompressWorker()) + .pipe(new DataLengthProbe("data_length")); + + var that = this; + worker.on("end", function () { + if (this.streamInfo['data_length'] !== that.uncompressedSize) { + throw new Error("Bug : uncompressed data size mismatch"); + } + }); + return worker; + }, + /** + * Create a worker to get the compressed content. + * @return {GenericWorker} the worker. + */ + getCompressedWorker: function () { + return new DataWorker(external.Promise.resolve(this.compressedContent)) + .withStreamInfo("compressedSize", this.compressedSize) + .withStreamInfo("uncompressedSize", this.uncompressedSize) + .withStreamInfo("crc32", this.crc32) + .withStreamInfo("compression", this.compression) + ; + } +}; + +/** + * Chain the given worker with other workers to compress the content with the + * given compression. + * @param {GenericWorker} uncompressedWorker the worker to pipe. + * @param {Object} compression the compression object. + * @param {Object} compressionOptions the options to use when compressing. + * @return {GenericWorker} the new worker compressing the content. + */ +CompressedObject.createWorkerFrom = function (uncompressedWorker, compression, compressionOptions) { + return uncompressedWorker + .pipe(new Crc32Probe()) + .pipe(new DataLengthProbe("uncompressedSize")) + .pipe(compression.compressWorker(compressionOptions)) + .pipe(new DataLengthProbe("compressedSize")) + .withStreamInfo("compression", compression); +}; + +module.exports = CompressedObject; + +},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(require,module,exports){ +'use strict'; + +var GenericWorker = require("./stream/GenericWorker"); + +exports.STORE = { + magic: "\x00\x00", + compressWorker : function (compressionOptions) { + return new GenericWorker("STORE compression"); + }, + uncompressWorker : function () { + return new GenericWorker("STORE decompression"); + } +}; +exports.DEFLATE = require('./flate'); + +},{"./flate":7,"./stream/GenericWorker":28}],4:[function(require,module,exports){ +'use strict'; + +var utils = require('./utils'); + +/** + * The following functions come from pako, from pako/lib/zlib/crc32.js + * released under the MIT license, see pako https://github.com/nodeca/pako/ + */ + +// Use ordinary array, since untyped makes no boost here +function makeTable() { + var c, table = []; + + for(var n =0; n < 256; n++){ + c = n; + for(var k =0; k < 8; k++){ + c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); + } + table[n] = c; + } + + return table; +} + +// Create table on load. Just 255 signed longs. Not a problem. +var crcTable = makeTable(); + + +function crc32(crc, buf, len, pos) { + var t = crcTable, end = pos + len; + + crc = crc ^ (-1); + + for (var i = pos; i < end; i++ ) { + crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; + } + + return (crc ^ (-1)); // >>> 0; +} + +// That's all for the pako functions. + +/** + * Compute the crc32 of a string. + * This is almost the same as the function crc32, but for strings. Using the + * same function for the two use cases leads to horrible performances. + * @param {Number} crc the starting value of the crc. + * @param {String} str the string to use. + * @param {Number} len the length of the string. + * @param {Number} pos the starting position for the crc32 computation. + * @return {Number} the computed crc32. + */ +function crc32str(crc, str, len, pos) { + var t = crcTable, end = pos + len; + + crc = crc ^ (-1); + + for (var i = pos; i < end; i++ ) { + crc = (crc >>> 8) ^ t[(crc ^ str.charCodeAt(i)) & 0xFF]; + } + + return (crc ^ (-1)); // >>> 0; +} + +module.exports = function crc32wrapper(input, crc) { + if (typeof input === "undefined" || !input.length) { + return 0; + } + + var isArray = utils.getTypeOf(input) !== "string"; + + if(isArray) { + return crc32(crc|0, input, input.length, 0); + } else { + return crc32str(crc|0, input, input.length, 0); + } +}; + +},{"./utils":32}],5:[function(require,module,exports){ +'use strict'; +exports.base64 = false; +exports.binary = false; +exports.dir = false; +exports.createFolders = true; +exports.date = null; +exports.compression = null; +exports.compressionOptions = null; +exports.comment = null; +exports.unixPermissions = null; +exports.dosPermissions = null; + +},{}],6:[function(require,module,exports){ +/* global Promise */ +'use strict'; + +// load the global object first: +// - it should be better integrated in the system (unhandledRejection in node) +// - the environment may have a custom Promise implementation (see zone.js) +var ES6Promise = null; +if (typeof Promise !== "undefined") { + ES6Promise = Promise; +} else { + ES6Promise = require("lie"); +} + +/** + * Let the user use/change some implementations. + */ +module.exports = { + Promise: ES6Promise +}; + +},{"lie":37}],7:[function(require,module,exports){ +'use strict'; +var USE_TYPEDARRAY = (typeof Uint8Array !== 'undefined') && (typeof Uint16Array !== 'undefined') && (typeof Uint32Array !== 'undefined'); + +var pako = require("pako"); +var utils = require("./utils"); +var GenericWorker = require("./stream/GenericWorker"); + +var ARRAY_TYPE = USE_TYPEDARRAY ? "uint8array" : "array"; + +exports.magic = "\x08\x00"; + +/** + * Create a worker that uses pako to inflate/deflate. + * @constructor + * @param {String} action the name of the pako function to call : either "Deflate" or "Inflate". + * @param {Object} options the options to use when (de)compressing. + */ +function FlateWorker(action, options) { + GenericWorker.call(this, "FlateWorker/" + action); + + this._pako = null; + this._pakoAction = action; + this._pakoOptions = options; + // the `meta` object from the last chunk received + // this allow this worker to pass around metadata + this.meta = {}; +} + +utils.inherits(FlateWorker, GenericWorker); + +/** + * @see GenericWorker.processChunk + */ +FlateWorker.prototype.processChunk = function (chunk) { + this.meta = chunk.meta; + if (this._pako === null) { + this._createPako(); + } + this._pako.push(utils.transformTo(ARRAY_TYPE, chunk.data), false); +}; + +/** + * @see GenericWorker.flush + */ +FlateWorker.prototype.flush = function () { + GenericWorker.prototype.flush.call(this); + if (this._pako === null) { + this._createPako(); + } + this._pako.push([], true); +}; +/** + * @see GenericWorker.cleanUp + */ +FlateWorker.prototype.cleanUp = function () { + GenericWorker.prototype.cleanUp.call(this); + this._pako = null; +}; + +/** + * Create the _pako object. + * TODO: lazy-loading this object isn't the best solution but it's the + * quickest. The best solution is to lazy-load the worker list. See also the + * issue #446. + */ +FlateWorker.prototype._createPako = function () { + this._pako = new pako[this._pakoAction]({ + raw: true, + level: this._pakoOptions.level || -1 // default compression + }); + var self = this; + this._pako.onData = function(data) { + self.push({ + data : data, + meta : self.meta + }); + }; +}; + +exports.compressWorker = function (compressionOptions) { + return new FlateWorker("Deflate", compressionOptions); +}; +exports.uncompressWorker = function () { + return new FlateWorker("Inflate", {}); +}; + +},{"./stream/GenericWorker":28,"./utils":32,"pako":38}],8:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var GenericWorker = require('../stream/GenericWorker'); +var utf8 = require('../utf8'); +var crc32 = require('../crc32'); +var signature = require('../signature'); + +/** + * Transform an integer into a string in hexadecimal. + * @private + * @param {number} dec the number to convert. + * @param {number} bytes the number of bytes to generate. + * @returns {string} the result. + */ +var decToHex = function(dec, bytes) { + var hex = "", i; + for (i = 0; i < bytes; i++) { + hex += String.fromCharCode(dec & 0xff); + dec = dec >>> 8; + } + return hex; +}; + +/** + * Generate the UNIX part of the external file attributes. + * @param {Object} unixPermissions the unix permissions or null. + * @param {Boolean} isDir true if the entry is a directory, false otherwise. + * @return {Number} a 32 bit integer. + * + * adapted from http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute : + * + * TTTTsstrwxrwxrwx0000000000ADVSHR + * ^^^^____________________________ file type, see zipinfo.c (UNX_*) + * ^^^_________________________ setuid, setgid, sticky + * ^^^^^^^^^________________ permissions + * ^^^^^^^^^^______ not used ? + * ^^^^^^ DOS attribute bits : Archive, Directory, Volume label, System file, Hidden, Read only + */ +var generateUnixExternalFileAttr = function (unixPermissions, isDir) { + + var result = unixPermissions; + if (!unixPermissions) { + // I can't use octal values in strict mode, hence the hexa. + // 040775 => 0x41fd + // 0100664 => 0x81b4 + result = isDir ? 0x41fd : 0x81b4; + } + return (result & 0xFFFF) << 16; +}; + +/** + * Generate the DOS part of the external file attributes. + * @param {Object} dosPermissions the dos permissions or null. + * @param {Boolean} isDir true if the entry is a directory, false otherwise. + * @return {Number} a 32 bit integer. + * + * Bit 0 Read-Only + * Bit 1 Hidden + * Bit 2 System + * Bit 3 Volume Label + * Bit 4 Directory + * Bit 5 Archive + */ +var generateDosExternalFileAttr = function (dosPermissions, isDir) { + + // the dir flag is already set for compatibility + return (dosPermissions || 0) & 0x3F; +}; + +/** + * Generate the various parts used in the construction of the final zip file. + * @param {Object} streamInfo the hash with information about the compressed file. + * @param {Boolean} streamedContent is the content streamed ? + * @param {Boolean} streamingEnded is the stream finished ? + * @param {number} offset the current offset from the start of the zip file. + * @param {String} platform let's pretend we are this platform (change platform dependents fields) + * @param {Function} encodeFileName the function to encode the file name / comment. + * @return {Object} the zip parts. + */ +var generateZipParts = function(streamInfo, streamedContent, streamingEnded, offset, platform, encodeFileName) { + var file = streamInfo['file'], + compression = streamInfo['compression'], + useCustomEncoding = encodeFileName !== utf8.utf8encode, + encodedFileName = utils.transformTo("string", encodeFileName(file.name)), + utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)), + comment = file.comment, + encodedComment = utils.transformTo("string", encodeFileName(comment)), + utfEncodedComment = utils.transformTo("string", utf8.utf8encode(comment)), + useUTF8ForFileName = utfEncodedFileName.length !== file.name.length, + useUTF8ForComment = utfEncodedComment.length !== comment.length, + dosTime, + dosDate, + extraFields = "", + unicodePathExtraField = "", + unicodeCommentExtraField = "", + dir = file.dir, + date = file.date; + + + var dataInfo = { + crc32 : 0, + compressedSize : 0, + uncompressedSize : 0 + }; + + // if the content is streamed, the sizes/crc32 are only available AFTER + // the end of the stream. + if (!streamedContent || streamingEnded) { + dataInfo.crc32 = streamInfo['crc32']; + dataInfo.compressedSize = streamInfo['compressedSize']; + dataInfo.uncompressedSize = streamInfo['uncompressedSize']; + } + + var bitflag = 0; + if (streamedContent) { + // Bit 3: the sizes/crc32 are set to zero in the local header. + // The correct values are put in the data descriptor immediately + // following the compressed data. + bitflag |= 0x0008; + } + if (!useCustomEncoding && (useUTF8ForFileName || useUTF8ForComment)) { + // Bit 11: Language encoding flag (EFS). + bitflag |= 0x0800; + } + + + var extFileAttr = 0; + var versionMadeBy = 0; + if (dir) { + // dos or unix, we set the dos dir flag + extFileAttr |= 0x00010; + } + if(platform === "UNIX") { + versionMadeBy = 0x031E; // UNIX, version 3.0 + extFileAttr |= generateUnixExternalFileAttr(file.unixPermissions, dir); + } else { // DOS or other, fallback to DOS + versionMadeBy = 0x0014; // DOS, version 2.0 + extFileAttr |= generateDosExternalFileAttr(file.dosPermissions, dir); + } + + // date + // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html + // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html + // @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html + + dosTime = date.getUTCHours(); + dosTime = dosTime << 6; + dosTime = dosTime | date.getUTCMinutes(); + dosTime = dosTime << 5; + dosTime = dosTime | date.getUTCSeconds() / 2; + + dosDate = date.getUTCFullYear() - 1980; + dosDate = dosDate << 4; + dosDate = dosDate | (date.getUTCMonth() + 1); + dosDate = dosDate << 5; + dosDate = dosDate | date.getUTCDate(); + + if (useUTF8ForFileName) { + // set the unicode path extra field. unzip needs at least one extra + // field to correctly handle unicode path, so using the path is as good + // as any other information. This could improve the situation with + // other archive managers too. + // This field is usually used without the utf8 flag, with a non + // unicode path in the header (winrar, winzip). This helps (a bit) + // with the messy Windows' default compressed folders feature but + // breaks on p7zip which doesn't seek the unicode path extra field. + // So for now, UTF-8 everywhere ! + unicodePathExtraField = + // Version + decToHex(1, 1) + + // NameCRC32 + decToHex(crc32(encodedFileName), 4) + + // UnicodeName + utfEncodedFileName; + + extraFields += + // Info-ZIP Unicode Path Extra Field + "\x75\x70" + + // size + decToHex(unicodePathExtraField.length, 2) + + // content + unicodePathExtraField; + } + + if(useUTF8ForComment) { + + unicodeCommentExtraField = + // Version + decToHex(1, 1) + + // CommentCRC32 + decToHex(crc32(encodedComment), 4) + + // UnicodeName + utfEncodedComment; + + extraFields += + // Info-ZIP Unicode Path Extra Field + "\x75\x63" + + // size + decToHex(unicodeCommentExtraField.length, 2) + + // content + unicodeCommentExtraField; + } + + var header = ""; + + // version needed to extract + header += "\x0A\x00"; + // general purpose bit flag + header += decToHex(bitflag, 2); + // compression method + header += compression.magic; + // last mod file time + header += decToHex(dosTime, 2); + // last mod file date + header += decToHex(dosDate, 2); + // crc-32 + header += decToHex(dataInfo.crc32, 4); + // compressed size + header += decToHex(dataInfo.compressedSize, 4); + // uncompressed size + header += decToHex(dataInfo.uncompressedSize, 4); + // file name length + header += decToHex(encodedFileName.length, 2); + // extra field length + header += decToHex(extraFields.length, 2); + + + var fileRecord = signature.LOCAL_FILE_HEADER + header + encodedFileName + extraFields; + + var dirRecord = signature.CENTRAL_FILE_HEADER + + // version made by (00: DOS) + decToHex(versionMadeBy, 2) + + // file header (common to file and central directory) + header + + // file comment length + decToHex(encodedComment.length, 2) + + // disk number start + "\x00\x00" + + // internal file attributes TODO + "\x00\x00" + + // external file attributes + decToHex(extFileAttr, 4) + + // relative offset of local header + decToHex(offset, 4) + + // file name + encodedFileName + + // extra field + extraFields + + // file comment + encodedComment; + + return { + fileRecord: fileRecord, + dirRecord: dirRecord + }; +}; + +/** + * Generate the EOCD record. + * @param {Number} entriesCount the number of entries in the zip file. + * @param {Number} centralDirLength the length (in bytes) of the central dir. + * @param {Number} localDirLength the length (in bytes) of the local dir. + * @param {String} comment the zip file comment as a binary string. + * @param {Function} encodeFileName the function to encode the comment. + * @return {String} the EOCD record. + */ +var generateCentralDirectoryEnd = function (entriesCount, centralDirLength, localDirLength, comment, encodeFileName) { + var dirEnd = ""; + var encodedComment = utils.transformTo("string", encodeFileName(comment)); + + // end of central dir signature + dirEnd = signature.CENTRAL_DIRECTORY_END + + // number of this disk + "\x00\x00" + + // number of the disk with the start of the central directory + "\x00\x00" + + // total number of entries in the central directory on this disk + decToHex(entriesCount, 2) + + // total number of entries in the central directory + decToHex(entriesCount, 2) + + // size of the central directory 4 bytes + decToHex(centralDirLength, 4) + + // offset of start of central directory with respect to the starting disk number + decToHex(localDirLength, 4) + + // .ZIP file comment length + decToHex(encodedComment.length, 2) + + // .ZIP file comment + encodedComment; + + return dirEnd; +}; + +/** + * Generate data descriptors for a file entry. + * @param {Object} streamInfo the hash generated by a worker, containing information + * on the file entry. + * @return {String} the data descriptors. + */ +var generateDataDescriptors = function (streamInfo) { + var descriptor = ""; + descriptor = signature.DATA_DESCRIPTOR + + // crc-32 4 bytes + decToHex(streamInfo['crc32'], 4) + + // compressed size 4 bytes + decToHex(streamInfo['compressedSize'], 4) + + // uncompressed size 4 bytes + decToHex(streamInfo['uncompressedSize'], 4); + + return descriptor; +}; + + +/** + * A worker to concatenate other workers to create a zip file. + * @param {Boolean} streamFiles `true` to stream the content of the files, + * `false` to accumulate it. + * @param {String} comment the comment to use. + * @param {String} platform the platform to use, "UNIX" or "DOS". + * @param {Function} encodeFileName the function to encode file names and comments. + */ +function ZipFileWorker(streamFiles, comment, platform, encodeFileName) { + GenericWorker.call(this, "ZipFileWorker"); + // The number of bytes written so far. This doesn't count accumulated chunks. + this.bytesWritten = 0; + // The comment of the zip file + this.zipComment = comment; + // The platform "generating" the zip file. + this.zipPlatform = platform; + // the function to encode file names and comments. + this.encodeFileName = encodeFileName; + // Should we stream the content of the files ? + this.streamFiles = streamFiles; + // If `streamFiles` is false, we will need to accumulate the content of the + // files to calculate sizes / crc32 (and write them *before* the content). + // This boolean indicates if we are accumulating chunks (it will change a lot + // during the lifetime of this worker). + this.accumulate = false; + // The buffer receiving chunks when accumulating content. + this.contentBuffer = []; + // The list of generated directory records. + this.dirRecords = []; + // The offset (in bytes) from the beginning of the zip file for the current source. + this.currentSourceOffset = 0; + // The total number of entries in this zip file. + this.entriesCount = 0; + // the name of the file currently being added, null when handling the end of the zip file. + // Used for the emitted metadata. + this.currentFile = null; + + + + this._sources = []; +} +utils.inherits(ZipFileWorker, GenericWorker); + +/** + * @see GenericWorker.push + */ +ZipFileWorker.prototype.push = function (chunk) { + + var currentFilePercent = chunk.meta.percent || 0; + var entriesCount = this.entriesCount; + var remainingFiles = this._sources.length; + + if(this.accumulate) { + this.contentBuffer.push(chunk); + } else { + this.bytesWritten += chunk.data.length; + + GenericWorker.prototype.push.call(this, { + data : chunk.data, + meta : { + currentFile : this.currentFile, + percent : entriesCount ? (currentFilePercent + 100 * (entriesCount - remainingFiles - 1)) / entriesCount : 100 + } + }); + } +}; + +/** + * The worker started a new source (an other worker). + * @param {Object} streamInfo the streamInfo object from the new source. + */ +ZipFileWorker.prototype.openedSource = function (streamInfo) { + this.currentSourceOffset = this.bytesWritten; + this.currentFile = streamInfo['file'].name; + + var streamedContent = this.streamFiles && !streamInfo['file'].dir; + + // don't stream folders (because they don't have any content) + if(streamedContent) { + var record = generateZipParts(streamInfo, streamedContent, false, this.currentSourceOffset, this.zipPlatform, this.encodeFileName); + this.push({ + data : record.fileRecord, + meta : {percent:0} + }); + } else { + // we need to wait for the whole file before pushing anything + this.accumulate = true; + } +}; + +/** + * The worker finished a source (an other worker). + * @param {Object} streamInfo the streamInfo object from the finished source. + */ +ZipFileWorker.prototype.closedSource = function (streamInfo) { + this.accumulate = false; + var streamedContent = this.streamFiles && !streamInfo['file'].dir; + var record = generateZipParts(streamInfo, streamedContent, true, this.currentSourceOffset, this.zipPlatform, this.encodeFileName); + + this.dirRecords.push(record.dirRecord); + if(streamedContent) { + // after the streamed file, we put data descriptors + this.push({ + data : generateDataDescriptors(streamInfo), + meta : {percent:100} + }); + } else { + // the content wasn't streamed, we need to push everything now + // first the file record, then the content + this.push({ + data : record.fileRecord, + meta : {percent:0} + }); + while(this.contentBuffer.length) { + this.push(this.contentBuffer.shift()); + } + } + this.currentFile = null; +}; + +/** + * @see GenericWorker.flush + */ +ZipFileWorker.prototype.flush = function () { + + var localDirLength = this.bytesWritten; + for(var i = 0; i < this.dirRecords.length; i++) { + this.push({ + data : this.dirRecords[i], + meta : {percent:100} + }); + } + var centralDirLength = this.bytesWritten - localDirLength; + + var dirEnd = generateCentralDirectoryEnd(this.dirRecords.length, centralDirLength, localDirLength, this.zipComment, this.encodeFileName); + + this.push({ + data : dirEnd, + meta : {percent:100} + }); +}; + +/** + * Prepare the next source to be read. + */ +ZipFileWorker.prototype.prepareNextSource = function () { + this.previous = this._sources.shift(); + this.openedSource(this.previous.streamInfo); + if (this.isPaused) { + this.previous.pause(); + } else { + this.previous.resume(); + } +}; + +/** + * @see GenericWorker.registerPrevious + */ +ZipFileWorker.prototype.registerPrevious = function (previous) { + this._sources.push(previous); + var self = this; + + previous.on('data', function (chunk) { + self.processChunk(chunk); + }); + previous.on('end', function () { + self.closedSource(self.previous.streamInfo); + if(self._sources.length) { + self.prepareNextSource(); + } else { + self.end(); + } + }); + previous.on('error', function (e) { + self.error(e); + }); + return this; +}; + +/** + * @see GenericWorker.resume + */ +ZipFileWorker.prototype.resume = function () { + if(!GenericWorker.prototype.resume.call(this)) { + return false; + } + + if (!this.previous && this._sources.length) { + this.prepareNextSource(); + return true; + } + if (!this.previous && !this._sources.length && !this.generatedError) { + this.end(); + return true; + } +}; + +/** + * @see GenericWorker.error + */ +ZipFileWorker.prototype.error = function (e) { + var sources = this._sources; + if(!GenericWorker.prototype.error.call(this, e)) { + return false; + } + for(var i = 0; i < sources.length; i++) { + try { + sources[i].error(e); + } catch(e) { + // the `error` exploded, nothing to do + } + } + return true; +}; + +/** + * @see GenericWorker.lock + */ +ZipFileWorker.prototype.lock = function () { + GenericWorker.prototype.lock.call(this); + var sources = this._sources; + for(var i = 0; i < sources.length; i++) { + sources[i].lock(); + } +}; + +module.exports = ZipFileWorker; + +},{"../crc32":4,"../signature":23,"../stream/GenericWorker":28,"../utf8":31,"../utils":32}],9:[function(require,module,exports){ +'use strict'; + +var compressions = require('../compressions'); +var ZipFileWorker = require('./ZipFileWorker'); + +/** + * Find the compression to use. + * @param {String} fileCompression the compression defined at the file level, if any. + * @param {String} zipCompression the compression defined at the load() level. + * @return {Object} the compression object to use. + */ +var getCompression = function (fileCompression, zipCompression) { + + var compressionName = fileCompression || zipCompression; + var compression = compressions[compressionName]; + if (!compression) { + throw new Error(compressionName + " is not a valid compression method !"); + } + return compression; +}; + +/** + * Create a worker to generate a zip file. + * @param {JSZip} zip the JSZip instance at the right root level. + * @param {Object} options to generate the zip file. + * @param {String} comment the comment to use. + */ +exports.generateWorker = function (zip, options, comment) { + + var zipFileWorker = new ZipFileWorker(options.streamFiles, comment, options.platform, options.encodeFileName); + var entriesCount = 0; + try { + + zip.forEach(function (relativePath, file) { + entriesCount++; + var compression = getCompression(file.options.compression, options.compression); + var compressionOptions = file.options.compressionOptions || options.compressionOptions || {}; + var dir = file.dir, date = file.date; + + file._compressWorker(compression, compressionOptions) + .withStreamInfo("file", { + name : relativePath, + dir : dir, + date : date, + comment : file.comment || "", + unixPermissions : file.unixPermissions, + dosPermissions : file.dosPermissions + }) + .pipe(zipFileWorker); + }); + zipFileWorker.entriesCount = entriesCount; + } catch (e) { + zipFileWorker.error(e); + } + + return zipFileWorker; +}; + +},{"../compressions":3,"./ZipFileWorker":8}],10:[function(require,module,exports){ +'use strict'; + +/** + * Representation a of zip file in js + * @constructor + */ +function JSZip() { + // if this constructor is used without `new`, it adds `new` before itself: + if(!(this instanceof JSZip)) { + return new JSZip(); + } + + if(arguments.length) { + throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide."); + } + + // object containing the files : + // { + // "folder/" : {...}, + // "folder/data.txt" : {...} + // } + // NOTE: we use a null prototype because we do not + // want filenames like "toString" coming from a zip file + // to overwrite methods and attributes in a normal Object. + this.files = Object.create(null); + + this.comment = null; + + // Where we are in the hierarchy + this.root = ""; + this.clone = function() { + var newObj = new JSZip(); + for (var i in this) { + if (typeof this[i] !== "function") { + newObj[i] = this[i]; + } + } + return newObj; + }; +} +JSZip.prototype = require('./object'); +JSZip.prototype.loadAsync = require('./load'); +JSZip.support = require('./support'); +JSZip.defaults = require('./defaults'); + +// TODO find a better way to handle this version, +// a require('package.json').version doesn't work with webpack, see #327 +JSZip.version = "3.7.1"; + +JSZip.loadAsync = function (content, options) { + return new JSZip().loadAsync(content, options); +}; + +JSZip.external = require("./external"); +module.exports = JSZip; + +},{"./defaults":5,"./external":6,"./load":11,"./object":15,"./support":30}],11:[function(require,module,exports){ +'use strict'; +var utils = require('./utils'); +var external = require("./external"); +var utf8 = require('./utf8'); +var ZipEntries = require('./zipEntries'); +var Crc32Probe = require('./stream/Crc32Probe'); +var nodejsUtils = require("./nodejsUtils"); + +/** + * Check the CRC32 of an entry. + * @param {ZipEntry} zipEntry the zip entry to check. + * @return {Promise} the result. + */ +function checkEntryCRC32(zipEntry) { + return new external.Promise(function (resolve, reject) { + var worker = zipEntry.decompressed.getContentWorker().pipe(new Crc32Probe()); + worker.on("error", function (e) { + reject(e); + }) + .on("end", function () { + if (worker.streamInfo.crc32 !== zipEntry.decompressed.crc32) { + reject(new Error("Corrupted zip : CRC32 mismatch")); + } else { + resolve(); + } + }) + .resume(); + }); +} + +module.exports = function (data, options) { + var zip = this; + options = utils.extend(options || {}, { + base64: false, + checkCRC32: false, + optimizedBinaryString: false, + createFolders: false, + decodeFileName: utf8.utf8decode + }); + + if (nodejsUtils.isNode && nodejsUtils.isStream(data)) { + return external.Promise.reject(new Error("JSZip can't accept a stream when loading a zip file.")); + } + + return utils.prepareContent("the loaded zip file", data, true, options.optimizedBinaryString, options.base64) + .then(function (data) { + var zipEntries = new ZipEntries(options); + zipEntries.load(data); + return zipEntries; + }).then(function checkCRC32(zipEntries) { + var promises = [external.Promise.resolve(zipEntries)]; + var files = zipEntries.files; + if (options.checkCRC32) { + for (var i = 0; i < files.length; i++) { + promises.push(checkEntryCRC32(files[i])); + } + } + return external.Promise.all(promises); + }).then(function addFiles(results) { + var zipEntries = results.shift(); + var files = zipEntries.files; + for (var i = 0; i < files.length; i++) { + var input = files[i]; + zip.file(input.fileNameStr, input.decompressed, { + binary: true, + optimizedBinaryString: true, + date: input.date, + dir: input.dir, + comment: input.fileCommentStr.length ? input.fileCommentStr : null, + unixPermissions: input.unixPermissions, + dosPermissions: input.dosPermissions, + createFolders: options.createFolders + }); + } + if (zipEntries.zipComment.length) { + zip.comment = zipEntries.zipComment; + } + + return zip; + }); +}; + +},{"./external":6,"./nodejsUtils":14,"./stream/Crc32Probe":25,"./utf8":31,"./utils":32,"./zipEntries":33}],12:[function(require,module,exports){ +"use strict"; + +var utils = require('../utils'); +var GenericWorker = require('../stream/GenericWorker'); + +/** + * A worker that use a nodejs stream as source. + * @constructor + * @param {String} filename the name of the file entry for this stream. + * @param {Readable} stream the nodejs stream. + */ +function NodejsStreamInputAdapter(filename, stream) { + GenericWorker.call(this, "Nodejs stream input adapter for " + filename); + this._upstreamEnded = false; + this._bindStream(stream); +} + +utils.inherits(NodejsStreamInputAdapter, GenericWorker); + +/** + * Prepare the stream and bind the callbacks on it. + * Do this ASAP on node 0.10 ! A lazy binding doesn't always work. + * @param {Stream} stream the nodejs stream to use. + */ +NodejsStreamInputAdapter.prototype._bindStream = function (stream) { + var self = this; + this._stream = stream; + stream.pause(); + stream + .on("data", function (chunk) { + self.push({ + data: chunk, + meta : { + percent : 0 + } + }); + }) + .on("error", function (e) { + if(self.isPaused) { + this.generatedError = e; + } else { + self.error(e); + } + }) + .on("end", function () { + if(self.isPaused) { + self._upstreamEnded = true; + } else { + self.end(); + } + }); +}; +NodejsStreamInputAdapter.prototype.pause = function () { + if(!GenericWorker.prototype.pause.call(this)) { + return false; + } + this._stream.pause(); + return true; +}; +NodejsStreamInputAdapter.prototype.resume = function () { + if(!GenericWorker.prototype.resume.call(this)) { + return false; + } + + if(this._upstreamEnded) { + this.end(); + } else { + this._stream.resume(); + } + + return true; +}; + +module.exports = NodejsStreamInputAdapter; + +},{"../stream/GenericWorker":28,"../utils":32}],13:[function(require,module,exports){ +'use strict'; + +var Readable = require('readable-stream').Readable; + +var utils = require('../utils'); +utils.inherits(NodejsStreamOutputAdapter, Readable); + +/** +* A nodejs stream using a worker as source. +* @see the SourceWrapper in http://nodejs.org/api/stream.html +* @constructor +* @param {StreamHelper} helper the helper wrapping the worker +* @param {Object} options the nodejs stream options +* @param {Function} updateCb the update callback. +*/ +function NodejsStreamOutputAdapter(helper, options, updateCb) { + Readable.call(this, options); + this._helper = helper; + + var self = this; + helper.on("data", function (data, meta) { + if (!self.push(data)) { + self._helper.pause(); + } + if(updateCb) { + updateCb(meta); + } + }) + .on("error", function(e) { + self.emit('error', e); + }) + .on("end", function () { + self.push(null); + }); +} + + +NodejsStreamOutputAdapter.prototype._read = function() { + this._helper.resume(); +}; + +module.exports = NodejsStreamOutputAdapter; + +},{"../utils":32,"readable-stream":16}],14:[function(require,module,exports){ +'use strict'; + +module.exports = { + /** + * True if this is running in Nodejs, will be undefined in a browser. + * In a browser, browserify won't include this file and the whole module + * will be resolved an empty object. + */ + isNode : typeof Buffer !== "undefined", + /** + * Create a new nodejs Buffer from an existing content. + * @param {Object} data the data to pass to the constructor. + * @param {String} encoding the encoding to use. + * @return {Buffer} a new Buffer. + */ + newBufferFrom: function(data, encoding) { + if (Buffer.from && Buffer.from !== Uint8Array.from) { + return Buffer.from(data, encoding); + } else { + if (typeof data === "number") { + // Safeguard for old Node.js versions. On newer versions, + // Buffer.from(number) / Buffer(number, encoding) already throw. + throw new Error("The \"data\" argument must not be a number"); + } + return new Buffer(data, encoding); + } + }, + /** + * Create a new nodejs Buffer with the specified size. + * @param {Integer} size the size of the buffer. + * @return {Buffer} a new Buffer. + */ + allocBuffer: function (size) { + if (Buffer.alloc) { + return Buffer.alloc(size); + } else { + var buf = new Buffer(size); + buf.fill(0); + return buf; + } + }, + /** + * Find out if an object is a Buffer. + * @param {Object} b the object to test. + * @return {Boolean} true if the object is a Buffer, false otherwise. + */ + isBuffer : function(b){ + return Buffer.isBuffer(b); + }, + + isStream : function (obj) { + return obj && + typeof obj.on === "function" && + typeof obj.pause === "function" && + typeof obj.resume === "function"; + } +}; + +},{}],15:[function(require,module,exports){ +'use strict'; +var utf8 = require('./utf8'); +var utils = require('./utils'); +var GenericWorker = require('./stream/GenericWorker'); +var StreamHelper = require('./stream/StreamHelper'); +var defaults = require('./defaults'); +var CompressedObject = require('./compressedObject'); +var ZipObject = require('./zipObject'); +var generate = require("./generate"); +var nodejsUtils = require("./nodejsUtils"); +var NodejsStreamInputAdapter = require("./nodejs/NodejsStreamInputAdapter"); + + +/** + * Add a file in the current folder. + * @private + * @param {string} name the name of the file + * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file + * @param {Object} originalOptions the options of the file + * @return {Object} the new file. + */ +var fileAdd = function(name, data, originalOptions) { + // be sure sub folders exist + var dataType = utils.getTypeOf(data), + parent; + + + /* + * Correct options. + */ + + var o = utils.extend(originalOptions || {}, defaults); + o.date = o.date || new Date(); + if (o.compression !== null) { + o.compression = o.compression.toUpperCase(); + } + + if (typeof o.unixPermissions === "string") { + o.unixPermissions = parseInt(o.unixPermissions, 8); + } + + // UNX_IFDIR 0040000 see zipinfo.c + if (o.unixPermissions && (o.unixPermissions & 0x4000)) { + o.dir = true; + } + // Bit 4 Directory + if (o.dosPermissions && (o.dosPermissions & 0x0010)) { + o.dir = true; + } + + if (o.dir) { + name = forceTrailingSlash(name); + } + if (o.createFolders && (parent = parentFolder(name))) { + folderAdd.call(this, parent, true); + } + + var isUnicodeString = dataType === "string" && o.binary === false && o.base64 === false; + if (!originalOptions || typeof originalOptions.binary === "undefined") { + o.binary = !isUnicodeString; + } + + + var isCompressedEmpty = (data instanceof CompressedObject) && data.uncompressedSize === 0; + + if (isCompressedEmpty || o.dir || !data || data.length === 0) { + o.base64 = false; + o.binary = true; + data = ""; + o.compression = "STORE"; + dataType = "string"; + } + + /* + * Convert content to fit. + */ + + var zipObjectContent = null; + if (data instanceof CompressedObject || data instanceof GenericWorker) { + zipObjectContent = data; + } else if (nodejsUtils.isNode && nodejsUtils.isStream(data)) { + zipObjectContent = new NodejsStreamInputAdapter(name, data); + } else { + zipObjectContent = utils.prepareContent(name, data, o.binary, o.optimizedBinaryString, o.base64); + } + + var object = new ZipObject(name, zipObjectContent, o); + this.files[name] = object; + /* + TODO: we can't throw an exception because we have async promises + (we can have a promise of a Date() for example) but returning a + promise is useless because file(name, data) returns the JSZip + object for chaining. Should we break that to allow the user + to catch the error ? + + return external.Promise.resolve(zipObjectContent) + .then(function () { + return object; + }); + */ +}; + +/** + * Find the parent folder of the path. + * @private + * @param {string} path the path to use + * @return {string} the parent folder, or "" + */ +var parentFolder = function (path) { + if (path.slice(-1) === '/') { + path = path.substring(0, path.length - 1); + } + var lastSlash = path.lastIndexOf('/'); + return (lastSlash > 0) ? path.substring(0, lastSlash) : ""; +}; + +/** + * Returns the path with a slash at the end. + * @private + * @param {String} path the path to check. + * @return {String} the path with a trailing slash. + */ +var forceTrailingSlash = function(path) { + // Check the name ends with a / + if (path.slice(-1) !== "/") { + path += "/"; // IE doesn't like substr(-1) + } + return path; +}; + +/** + * Add a (sub) folder in the current folder. + * @private + * @param {string} name the folder's name + * @param {boolean=} [createFolders] If true, automatically create sub + * folders. Defaults to false. + * @return {Object} the new folder. + */ +var folderAdd = function(name, createFolders) { + createFolders = (typeof createFolders !== 'undefined') ? createFolders : defaults.createFolders; + + name = forceTrailingSlash(name); + + // Does this folder already exist? + if (!this.files[name]) { + fileAdd.call(this, name, null, { + dir: true, + createFolders: createFolders + }); + } + return this.files[name]; +}; + +/** +* Cross-window, cross-Node-context regular expression detection +* @param {Object} object Anything +* @return {Boolean} true if the object is a regular expression, +* false otherwise +*/ +function isRegExp(object) { + return Object.prototype.toString.call(object) === "[object RegExp]"; +} + +// return the actual prototype of JSZip +var out = { + /** + * @see loadAsync + */ + load: function() { + throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide."); + }, + + + /** + * Call a callback function for each entry at this folder level. + * @param {Function} cb the callback function: + * function (relativePath, file) {...} + * It takes 2 arguments : the relative path and the file. + */ + forEach: function(cb) { + var filename, relativePath, file; + /* jshint ignore:start */ + // ignore warning about unwanted properties because this.files is a null prototype object + for (filename in this.files) { + file = this.files[filename]; + relativePath = filename.slice(this.root.length, filename.length); + if (relativePath && filename.slice(0, this.root.length) === this.root) { // the file is in the current root + cb(relativePath, file); // TODO reverse the parameters ? need to be clean AND consistent with the filter search fn... + } + } + /* jshint ignore:end */ + }, + + /** + * Filter nested files/folders with the specified function. + * @param {Function} search the predicate to use : + * function (relativePath, file) {...} + * It takes 2 arguments : the relative path and the file. + * @return {Array} An array of matching elements. + */ + filter: function(search) { + var result = []; + this.forEach(function (relativePath, entry) { + if (search(relativePath, entry)) { // the file matches the function + result.push(entry); + } + + }); + return result; + }, + + /** + * Add a file to the zip file, or search a file. + * @param {string|RegExp} name The name of the file to add (if data is defined), + * the name of the file to find (if no data) or a regex to match files. + * @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded + * @param {Object} o File options + * @return {JSZip|Object|Array} this JSZip object (when adding a file), + * a file (when searching by string) or an array of files (when searching by regex). + */ + file: function(name, data, o) { + if (arguments.length === 1) { + if (isRegExp(name)) { + var regexp = name; + return this.filter(function(relativePath, file) { + return !file.dir && regexp.test(relativePath); + }); + } + else { // text + var obj = this.files[this.root + name]; + if (obj && !obj.dir) { + return obj; + } else { + return null; + } + } + } + else { // more than one argument : we have data ! + name = this.root + name; + fileAdd.call(this, name, data, o); + } + return this; + }, + + /** + * Add a directory to the zip file, or search. + * @param {String|RegExp} arg The name of the directory to add, or a regex to search folders. + * @return {JSZip} an object with the new directory as the root, or an array containing matching folders. + */ + folder: function(arg) { + if (!arg) { + return this; + } + + if (isRegExp(arg)) { + return this.filter(function(relativePath, file) { + return file.dir && arg.test(relativePath); + }); + } + + // else, name is a new folder + var name = this.root + arg; + var newFolder = folderAdd.call(this, name); + + // Allow chaining by returning a new object with this folder as the root + var ret = this.clone(); + ret.root = newFolder.name; + return ret; + }, + + /** + * Delete a file, or a directory and all sub-files, from the zip + * @param {string} name the name of the file to delete + * @return {JSZip} this JSZip object + */ + remove: function(name) { + name = this.root + name; + var file = this.files[name]; + if (!file) { + // Look for any folders + if (name.slice(-1) !== "/") { + name += "/"; + } + file = this.files[name]; + } + + if (file && !file.dir) { + // file + delete this.files[name]; + } else { + // maybe a folder, delete recursively + var kids = this.filter(function(relativePath, file) { + return file.name.slice(0, name.length) === name; + }); + for (var i = 0; i < kids.length; i++) { + delete this.files[kids[i].name]; + } + } + + return this; + }, + + /** + * Generate the complete zip file + * @param {Object} options the options to generate the zip file : + * - compression, "STORE" by default. + * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob. + * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file + */ + generate: function(options) { + throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide."); + }, + + /** + * Generate the complete zip file as an internal stream. + * @param {Object} options the options to generate the zip file : + * - compression, "STORE" by default. + * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob. + * @return {StreamHelper} the streamed zip file. + */ + generateInternalStream: function(options) { + var worker, opts = {}; + try { + opts = utils.extend(options || {}, { + streamFiles: false, + compression: "STORE", + compressionOptions : null, + type: "", + platform: "DOS", + comment: null, + mimeType: 'application/zip', + encodeFileName: utf8.utf8encode + }); + + opts.type = opts.type.toLowerCase(); + opts.compression = opts.compression.toUpperCase(); + + // "binarystring" is preferred but the internals use "string". + if(opts.type === "binarystring") { + opts.type = "string"; + } + + if (!opts.type) { + throw new Error("No output type specified."); + } + + utils.checkSupport(opts.type); + + // accept nodejs `process.platform` + if( + opts.platform === 'darwin' || + opts.platform === 'freebsd' || + opts.platform === 'linux' || + opts.platform === 'sunos' + ) { + opts.platform = "UNIX"; + } + if (opts.platform === 'win32') { + opts.platform = "DOS"; + } + + var comment = opts.comment || this.comment || ""; + worker = generate.generateWorker(this, opts, comment); + } catch (e) { + worker = new GenericWorker("error"); + worker.error(e); + } + return new StreamHelper(worker, opts.type || "string", opts.mimeType); + }, + /** + * Generate the complete zip file asynchronously. + * @see generateInternalStream + */ + generateAsync: function(options, onUpdate) { + return this.generateInternalStream(options).accumulate(onUpdate); + }, + /** + * Generate the complete zip file asynchronously. + * @see generateInternalStream + */ + generateNodeStream: function(options, onUpdate) { + options = options || {}; + if (!options.type) { + options.type = "nodebuffer"; + } + return this.generateInternalStream(options).toNodejsStream(onUpdate); + } +}; +module.exports = out; + +},{"./compressedObject":2,"./defaults":5,"./generate":9,"./nodejs/NodejsStreamInputAdapter":12,"./nodejsUtils":14,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31,"./utils":32,"./zipObject":35}],16:[function(require,module,exports){ +/* + * This file is used by module bundlers (browserify/webpack/etc) when + * including a stream implementation. We use "readable-stream" to get a + * consistent behavior between nodejs versions but bundlers often have a shim + * for "stream". Using this shim greatly improve the compatibility and greatly + * reduce the final size of the bundle (only one stream implementation, not + * two). + */ +module.exports = require("stream"); + +},{"stream":undefined}],17:[function(require,module,exports){ +'use strict'; +var DataReader = require('./DataReader'); +var utils = require('../utils'); + +function ArrayReader(data) { + DataReader.call(this, data); + for(var i = 0; i < this.data.length; i++) { + data[i] = data[i] & 0xFF; + } +} +utils.inherits(ArrayReader, DataReader); +/** + * @see DataReader.byteAt + */ +ArrayReader.prototype.byteAt = function(i) { + return this.data[this.zero + i]; +}; +/** + * @see DataReader.lastIndexOfSignature + */ +ArrayReader.prototype.lastIndexOfSignature = function(sig) { + var sig0 = sig.charCodeAt(0), + sig1 = sig.charCodeAt(1), + sig2 = sig.charCodeAt(2), + sig3 = sig.charCodeAt(3); + for (var i = this.length - 4; i >= 0; --i) { + if (this.data[i] === sig0 && this.data[i + 1] === sig1 && this.data[i + 2] === sig2 && this.data[i + 3] === sig3) { + return i - this.zero; + } + } + + return -1; +}; +/** + * @see DataReader.readAndCheckSignature + */ +ArrayReader.prototype.readAndCheckSignature = function (sig) { + var sig0 = sig.charCodeAt(0), + sig1 = sig.charCodeAt(1), + sig2 = sig.charCodeAt(2), + sig3 = sig.charCodeAt(3), + data = this.readData(4); + return sig0 === data[0] && sig1 === data[1] && sig2 === data[2] && sig3 === data[3]; +}; +/** + * @see DataReader.readData + */ +ArrayReader.prototype.readData = function(size) { + this.checkOffset(size); + if(size === 0) { + return []; + } + var result = this.data.slice(this.zero + this.index, this.zero + this.index + size); + this.index += size; + return result; +}; +module.exports = ArrayReader; + +},{"../utils":32,"./DataReader":18}],18:[function(require,module,exports){ +'use strict'; +var utils = require('../utils'); + +function DataReader(data) { + this.data = data; // type : see implementation + this.length = data.length; + this.index = 0; + this.zero = 0; +} +DataReader.prototype = { + /** + * Check that the offset will not go too far. + * @param {string} offset the additional offset to check. + * @throws {Error} an Error if the offset is out of bounds. + */ + checkOffset: function(offset) { + this.checkIndex(this.index + offset); + }, + /** + * Check that the specified index will not be too far. + * @param {string} newIndex the index to check. + * @throws {Error} an Error if the index is out of bounds. + */ + checkIndex: function(newIndex) { + if (this.length < this.zero + newIndex || newIndex < 0) { + throw new Error("End of data reached (data length = " + this.length + ", asked index = " + (newIndex) + "). Corrupted zip ?"); + } + }, + /** + * Change the index. + * @param {number} newIndex The new index. + * @throws {Error} if the new index is out of the data. + */ + setIndex: function(newIndex) { + this.checkIndex(newIndex); + this.index = newIndex; + }, + /** + * Skip the next n bytes. + * @param {number} n the number of bytes to skip. + * @throws {Error} if the new index is out of the data. + */ + skip: function(n) { + this.setIndex(this.index + n); + }, + /** + * Get the byte at the specified index. + * @param {number} i the index to use. + * @return {number} a byte. + */ + byteAt: function(i) { + // see implementations + }, + /** + * Get the next number with a given byte size. + * @param {number} size the number of bytes to read. + * @return {number} the corresponding number. + */ + readInt: function(size) { + var result = 0, + i; + this.checkOffset(size); + for (i = this.index + size - 1; i >= this.index; i--) { + result = (result << 8) + this.byteAt(i); + } + this.index += size; + return result; + }, + /** + * Get the next string with a given byte size. + * @param {number} size the number of bytes to read. + * @return {string} the corresponding string. + */ + readString: function(size) { + return utils.transformTo("string", this.readData(size)); + }, + /** + * Get raw data without conversion, bytes. + * @param {number} size the number of bytes to read. + * @return {Object} the raw data, implementation specific. + */ + readData: function(size) { + // see implementations + }, + /** + * Find the last occurrence of a zip signature (4 bytes). + * @param {string} sig the signature to find. + * @return {number} the index of the last occurrence, -1 if not found. + */ + lastIndexOfSignature: function(sig) { + // see implementations + }, + /** + * Read the signature (4 bytes) at the current position and compare it with sig. + * @param {string} sig the expected signature + * @return {boolean} true if the signature matches, false otherwise. + */ + readAndCheckSignature: function(sig) { + // see implementations + }, + /** + * Get the next date. + * @return {Date} the date. + */ + readDate: function() { + var dostime = this.readInt(4); + return new Date(Date.UTC( + ((dostime >> 25) & 0x7f) + 1980, // year + ((dostime >> 21) & 0x0f) - 1, // month + (dostime >> 16) & 0x1f, // day + (dostime >> 11) & 0x1f, // hour + (dostime >> 5) & 0x3f, // minute + (dostime & 0x1f) << 1)); // second + } +}; +module.exports = DataReader; + +},{"../utils":32}],19:[function(require,module,exports){ +'use strict'; +var Uint8ArrayReader = require('./Uint8ArrayReader'); +var utils = require('../utils'); + +function NodeBufferReader(data) { + Uint8ArrayReader.call(this, data); +} +utils.inherits(NodeBufferReader, Uint8ArrayReader); + +/** + * @see DataReader.readData + */ +NodeBufferReader.prototype.readData = function(size) { + this.checkOffset(size); + var result = this.data.slice(this.zero + this.index, this.zero + this.index + size); + this.index += size; + return result; +}; +module.exports = NodeBufferReader; + +},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(require,module,exports){ +'use strict'; +var DataReader = require('./DataReader'); +var utils = require('../utils'); + +function StringReader(data) { + DataReader.call(this, data); +} +utils.inherits(StringReader, DataReader); +/** + * @see DataReader.byteAt + */ +StringReader.prototype.byteAt = function(i) { + return this.data.charCodeAt(this.zero + i); +}; +/** + * @see DataReader.lastIndexOfSignature + */ +StringReader.prototype.lastIndexOfSignature = function(sig) { + return this.data.lastIndexOf(sig) - this.zero; +}; +/** + * @see DataReader.readAndCheckSignature + */ +StringReader.prototype.readAndCheckSignature = function (sig) { + var data = this.readData(4); + return sig === data; +}; +/** + * @see DataReader.readData + */ +StringReader.prototype.readData = function(size) { + this.checkOffset(size); + // this will work because the constructor applied the "& 0xff" mask. + var result = this.data.slice(this.zero + this.index, this.zero + this.index + size); + this.index += size; + return result; +}; +module.exports = StringReader; + +},{"../utils":32,"./DataReader":18}],21:[function(require,module,exports){ +'use strict'; +var ArrayReader = require('./ArrayReader'); +var utils = require('../utils'); + +function Uint8ArrayReader(data) { + ArrayReader.call(this, data); +} +utils.inherits(Uint8ArrayReader, ArrayReader); +/** + * @see DataReader.readData + */ +Uint8ArrayReader.prototype.readData = function(size) { + this.checkOffset(size); + if(size === 0) { + // in IE10, when using subarray(idx, idx), we get the array [0x00] instead of []. + return new Uint8Array(0); + } + var result = this.data.subarray(this.zero + this.index, this.zero + this.index + size); + this.index += size; + return result; +}; +module.exports = Uint8ArrayReader; + +},{"../utils":32,"./ArrayReader":17}],22:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var support = require('../support'); +var ArrayReader = require('./ArrayReader'); +var StringReader = require('./StringReader'); +var NodeBufferReader = require('./NodeBufferReader'); +var Uint8ArrayReader = require('./Uint8ArrayReader'); + +/** + * Create a reader adapted to the data. + * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data to read. + * @return {DataReader} the data reader. + */ +module.exports = function (data) { + var type = utils.getTypeOf(data); + utils.checkSupport(type); + if (type === "string" && !support.uint8array) { + return new StringReader(data); + } + if (type === "nodebuffer") { + return new NodeBufferReader(data); + } + if (support.uint8array) { + return new Uint8ArrayReader(utils.transformTo("uint8array", data)); + } + return new ArrayReader(utils.transformTo("array", data)); +}; + +},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(require,module,exports){ +'use strict'; +exports.LOCAL_FILE_HEADER = "PK\x03\x04"; +exports.CENTRAL_FILE_HEADER = "PK\x01\x02"; +exports.CENTRAL_DIRECTORY_END = "PK\x05\x06"; +exports.ZIP64_CENTRAL_DIRECTORY_LOCATOR = "PK\x06\x07"; +exports.ZIP64_CENTRAL_DIRECTORY_END = "PK\x06\x06"; +exports.DATA_DESCRIPTOR = "PK\x07\x08"; + +},{}],24:[function(require,module,exports){ +'use strict'; + +var GenericWorker = require('./GenericWorker'); +var utils = require('../utils'); + +/** + * A worker which convert chunks to a specified type. + * @constructor + * @param {String} destType the destination type. + */ +function ConvertWorker(destType) { + GenericWorker.call(this, "ConvertWorker to " + destType); + this.destType = destType; +} +utils.inherits(ConvertWorker, GenericWorker); + +/** + * @see GenericWorker.processChunk + */ +ConvertWorker.prototype.processChunk = function (chunk) { + this.push({ + data : utils.transformTo(this.destType, chunk.data), + meta : chunk.meta + }); +}; +module.exports = ConvertWorker; + +},{"../utils":32,"./GenericWorker":28}],25:[function(require,module,exports){ +'use strict'; + +var GenericWorker = require('./GenericWorker'); +var crc32 = require('../crc32'); +var utils = require('../utils'); + +/** + * A worker which calculate the crc32 of the data flowing through. + * @constructor + */ +function Crc32Probe() { + GenericWorker.call(this, "Crc32Probe"); + this.withStreamInfo("crc32", 0); +} +utils.inherits(Crc32Probe, GenericWorker); + +/** + * @see GenericWorker.processChunk + */ +Crc32Probe.prototype.processChunk = function (chunk) { + this.streamInfo.crc32 = crc32(chunk.data, this.streamInfo.crc32 || 0); + this.push(chunk); +}; +module.exports = Crc32Probe; + +},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var GenericWorker = require('./GenericWorker'); + +/** + * A worker which calculate the total length of the data flowing through. + * @constructor + * @param {String} propName the name used to expose the length + */ +function DataLengthProbe(propName) { + GenericWorker.call(this, "DataLengthProbe for " + propName); + this.propName = propName; + this.withStreamInfo(propName, 0); +} +utils.inherits(DataLengthProbe, GenericWorker); + +/** + * @see GenericWorker.processChunk + */ +DataLengthProbe.prototype.processChunk = function (chunk) { + if(chunk) { + var length = this.streamInfo[this.propName] || 0; + this.streamInfo[this.propName] = length + chunk.data.length; + } + GenericWorker.prototype.processChunk.call(this, chunk); +}; +module.exports = DataLengthProbe; + + +},{"../utils":32,"./GenericWorker":28}],27:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var GenericWorker = require('./GenericWorker'); + +// the size of the generated chunks +// TODO expose this as a public variable +var DEFAULT_BLOCK_SIZE = 16 * 1024; + +/** + * A worker that reads a content and emits chunks. + * @constructor + * @param {Promise} dataP the promise of the data to split + */ +function DataWorker(dataP) { + GenericWorker.call(this, "DataWorker"); + var self = this; + this.dataIsReady = false; + this.index = 0; + this.max = 0; + this.data = null; + this.type = ""; + + this._tickScheduled = false; + + dataP.then(function (data) { + self.dataIsReady = true; + self.data = data; + self.max = data && data.length || 0; + self.type = utils.getTypeOf(data); + if(!self.isPaused) { + self._tickAndRepeat(); + } + }, function (e) { + self.error(e); + }); +} + +utils.inherits(DataWorker, GenericWorker); + +/** + * @see GenericWorker.cleanUp + */ +DataWorker.prototype.cleanUp = function () { + GenericWorker.prototype.cleanUp.call(this); + this.data = null; +}; + +/** + * @see GenericWorker.resume + */ +DataWorker.prototype.resume = function () { + if(!GenericWorker.prototype.resume.call(this)) { + return false; + } + + if (!this._tickScheduled && this.dataIsReady) { + this._tickScheduled = true; + utils.delay(this._tickAndRepeat, [], this); + } + return true; +}; + +/** + * Trigger a tick a schedule an other call to this function. + */ +DataWorker.prototype._tickAndRepeat = function() { + this._tickScheduled = false; + if(this.isPaused || this.isFinished) { + return; + } + this._tick(); + if(!this.isFinished) { + utils.delay(this._tickAndRepeat, [], this); + this._tickScheduled = true; + } +}; + +/** + * Read and push a chunk. + */ +DataWorker.prototype._tick = function() { + + if(this.isPaused || this.isFinished) { + return false; + } + + var size = DEFAULT_BLOCK_SIZE; + var data = null, nextIndex = Math.min(this.max, this.index + size); + if (this.index >= this.max) { + // EOF + return this.end(); + } else { + switch(this.type) { + case "string": + data = this.data.substring(this.index, nextIndex); + break; + case "uint8array": + data = this.data.subarray(this.index, nextIndex); + break; + case "array": + case "nodebuffer": + data = this.data.slice(this.index, nextIndex); + break; + } + this.index = nextIndex; + return this.push({ + data : data, + meta : { + percent : this.max ? this.index / this.max * 100 : 0 + } + }); + } +}; + +module.exports = DataWorker; + +},{"../utils":32,"./GenericWorker":28}],28:[function(require,module,exports){ +'use strict'; + +/** + * A worker that does nothing but passing chunks to the next one. This is like + * a nodejs stream but with some differences. On the good side : + * - it works on IE 6-9 without any issue / polyfill + * - it weights less than the full dependencies bundled with browserify + * - it forwards errors (no need to declare an error handler EVERYWHERE) + * + * A chunk is an object with 2 attributes : `meta` and `data`. The former is an + * object containing anything (`percent` for example), see each worker for more + * details. The latter is the real data (String, Uint8Array, etc). + * + * @constructor + * @param {String} name the name of the stream (mainly used for debugging purposes) + */ +function GenericWorker(name) { + // the name of the worker + this.name = name || "default"; + // an object containing metadata about the workers chain + this.streamInfo = {}; + // an error which happened when the worker was paused + this.generatedError = null; + // an object containing metadata to be merged by this worker into the general metadata + this.extraStreamInfo = {}; + // true if the stream is paused (and should not do anything), false otherwise + this.isPaused = true; + // true if the stream is finished (and should not do anything), false otherwise + this.isFinished = false; + // true if the stream is locked to prevent further structure updates (pipe), false otherwise + this.isLocked = false; + // the event listeners + this._listeners = { + 'data':[], + 'end':[], + 'error':[] + }; + // the previous worker, if any + this.previous = null; +} + +GenericWorker.prototype = { + /** + * Push a chunk to the next workers. + * @param {Object} chunk the chunk to push + */ + push : function (chunk) { + this.emit("data", chunk); + }, + /** + * End the stream. + * @return {Boolean} true if this call ended the worker, false otherwise. + */ + end : function () { + if (this.isFinished) { + return false; + } + + this.flush(); + try { + this.emit("end"); + this.cleanUp(); + this.isFinished = true; + } catch (e) { + this.emit("error", e); + } + return true; + }, + /** + * End the stream with an error. + * @param {Error} e the error which caused the premature end. + * @return {Boolean} true if this call ended the worker with an error, false otherwise. + */ + error : function (e) { + if (this.isFinished) { + return false; + } + + if(this.isPaused) { + this.generatedError = e; + } else { + this.isFinished = true; + + this.emit("error", e); + + // in the workers chain exploded in the middle of the chain, + // the error event will go downward but we also need to notify + // workers upward that there has been an error. + if(this.previous) { + this.previous.error(e); + } + + this.cleanUp(); + } + return true; + }, + /** + * Add a callback on an event. + * @param {String} name the name of the event (data, end, error) + * @param {Function} listener the function to call when the event is triggered + * @return {GenericWorker} the current object for chainability + */ + on : function (name, listener) { + this._listeners[name].push(listener); + return this; + }, + /** + * Clean any references when a worker is ending. + */ + cleanUp : function () { + this.streamInfo = this.generatedError = this.extraStreamInfo = null; + this._listeners = []; + }, + /** + * Trigger an event. This will call registered callback with the provided arg. + * @param {String} name the name of the event (data, end, error) + * @param {Object} arg the argument to call the callback with. + */ + emit : function (name, arg) { + if (this._listeners[name]) { + for(var i = 0; i < this._listeners[name].length; i++) { + this._listeners[name][i].call(this, arg); + } + } + }, + /** + * Chain a worker with an other. + * @param {Worker} next the worker receiving events from the current one. + * @return {worker} the next worker for chainability + */ + pipe : function (next) { + return next.registerPrevious(this); + }, + /** + * Same as `pipe` in the other direction. + * Using an API with `pipe(next)` is very easy. + * Implementing the API with the point of view of the next one registering + * a source is easier, see the ZipFileWorker. + * @param {Worker} previous the previous worker, sending events to this one + * @return {Worker} the current worker for chainability + */ + registerPrevious : function (previous) { + if (this.isLocked) { + throw new Error("The stream '" + this + "' has already been used."); + } + + // sharing the streamInfo... + this.streamInfo = previous.streamInfo; + // ... and adding our own bits + this.mergeStreamInfo(); + this.previous = previous; + var self = this; + previous.on('data', function (chunk) { + self.processChunk(chunk); + }); + previous.on('end', function () { + self.end(); + }); + previous.on('error', function (e) { + self.error(e); + }); + return this; + }, + /** + * Pause the stream so it doesn't send events anymore. + * @return {Boolean} true if this call paused the worker, false otherwise. + */ + pause : function () { + if(this.isPaused || this.isFinished) { + return false; + } + this.isPaused = true; + + if(this.previous) { + this.previous.pause(); + } + return true; + }, + /** + * Resume a paused stream. + * @return {Boolean} true if this call resumed the worker, false otherwise. + */ + resume : function () { + if(!this.isPaused || this.isFinished) { + return false; + } + this.isPaused = false; + + // if true, the worker tried to resume but failed + var withError = false; + if(this.generatedError) { + this.error(this.generatedError); + withError = true; + } + if(this.previous) { + this.previous.resume(); + } + + return !withError; + }, + /** + * Flush any remaining bytes as the stream is ending. + */ + flush : function () {}, + /** + * Process a chunk. This is usually the method overridden. + * @param {Object} chunk the chunk to process. + */ + processChunk : function(chunk) { + this.push(chunk); + }, + /** + * Add a key/value to be added in the workers chain streamInfo once activated. + * @param {String} key the key to use + * @param {Object} value the associated value + * @return {Worker} the current worker for chainability + */ + withStreamInfo : function (key, value) { + this.extraStreamInfo[key] = value; + this.mergeStreamInfo(); + return this; + }, + /** + * Merge this worker's streamInfo into the chain's streamInfo. + */ + mergeStreamInfo : function () { + for(var key in this.extraStreamInfo) { + if (!this.extraStreamInfo.hasOwnProperty(key)) { + continue; + } + this.streamInfo[key] = this.extraStreamInfo[key]; + } + }, + + /** + * Lock the stream to prevent further updates on the workers chain. + * After calling this method, all calls to pipe will fail. + */ + lock: function () { + if (this.isLocked) { + throw new Error("The stream '" + this + "' has already been used."); + } + this.isLocked = true; + if (this.previous) { + this.previous.lock(); + } + }, + + /** + * + * Pretty print the workers chain. + */ + toString : function () { + var me = "Worker " + this.name; + if (this.previous) { + return this.previous + " -> " + me; + } else { + return me; + } + } +}; + +module.exports = GenericWorker; + +},{}],29:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var ConvertWorker = require('./ConvertWorker'); +var GenericWorker = require('./GenericWorker'); +var base64 = require('../base64'); +var support = require("../support"); +var external = require("../external"); + +var NodejsStreamOutputAdapter = null; +if (support.nodestream) { + try { + NodejsStreamOutputAdapter = require('../nodejs/NodejsStreamOutputAdapter'); + } catch(e) {} +} + +/** + * Apply the final transformation of the data. If the user wants a Blob for + * example, it's easier to work with an U8intArray and finally do the + * ArrayBuffer/Blob conversion. + * @param {String} type the name of the final type + * @param {String|Uint8Array|Buffer} content the content to transform + * @param {String} mimeType the mime type of the content, if applicable. + * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the content in the right format. + */ +function transformZipOutput(type, content, mimeType) { + switch(type) { + case "blob" : + return utils.newBlob(utils.transformTo("arraybuffer", content), mimeType); + case "base64" : + return base64.encode(content); + default : + return utils.transformTo(type, content); + } +} + +/** + * Concatenate an array of data of the given type. + * @param {String} type the type of the data in the given array. + * @param {Array} dataArray the array containing the data chunks to concatenate + * @return {String|Uint8Array|Buffer} the concatenated data + * @throws Error if the asked type is unsupported + */ +function concat (type, dataArray) { + var i, index = 0, res = null, totalLength = 0; + for(i = 0; i < dataArray.length; i++) { + totalLength += dataArray[i].length; + } + switch(type) { + case "string": + return dataArray.join(""); + case "array": + return Array.prototype.concat.apply([], dataArray); + case "uint8array": + res = new Uint8Array(totalLength); + for(i = 0; i < dataArray.length; i++) { + res.set(dataArray[i], index); + index += dataArray[i].length; + } + return res; + case "nodebuffer": + return Buffer.concat(dataArray); + default: + throw new Error("concat : unsupported type '" + type + "'"); + } +} + +/** + * Listen a StreamHelper, accumulate its content and concatenate it into a + * complete block. + * @param {StreamHelper} helper the helper to use. + * @param {Function} updateCallback a callback called on each update. Called + * with one arg : + * - the metadata linked to the update received. + * @return Promise the promise for the accumulation. + */ +function accumulate(helper, updateCallback) { + return new external.Promise(function (resolve, reject){ + var dataArray = []; + var chunkType = helper._internalType, + resultType = helper._outputType, + mimeType = helper._mimeType; + helper + .on('data', function (data, meta) { + dataArray.push(data); + if(updateCallback) { + updateCallback(meta); + } + }) + .on('error', function(err) { + dataArray = []; + reject(err); + }) + .on('end', function (){ + try { + var result = transformZipOutput(resultType, concat(chunkType, dataArray), mimeType); + resolve(result); + } catch (e) { + reject(e); + } + dataArray = []; + }) + .resume(); + }); +} + +/** + * An helper to easily use workers outside of JSZip. + * @constructor + * @param {Worker} worker the worker to wrap + * @param {String} outputType the type of data expected by the use + * @param {String} mimeType the mime type of the content, if applicable. + */ +function StreamHelper(worker, outputType, mimeType) { + var internalType = outputType; + switch(outputType) { + case "blob": + case "arraybuffer": + internalType = "uint8array"; + break; + case "base64": + internalType = "string"; + break; + } + + try { + // the type used internally + this._internalType = internalType; + // the type used to output results + this._outputType = outputType; + // the mime type + this._mimeType = mimeType; + utils.checkSupport(internalType); + this._worker = worker.pipe(new ConvertWorker(internalType)); + // the last workers can be rewired without issues but we need to + // prevent any updates on previous workers. + worker.lock(); + } catch(e) { + this._worker = new GenericWorker("error"); + this._worker.error(e); + } +} + +StreamHelper.prototype = { + /** + * Listen a StreamHelper, accumulate its content and concatenate it into a + * complete block. + * @param {Function} updateCb the update callback. + * @return Promise the promise for the accumulation. + */ + accumulate : function (updateCb) { + return accumulate(this, updateCb); + }, + /** + * Add a listener on an event triggered on a stream. + * @param {String} evt the name of the event + * @param {Function} fn the listener + * @return {StreamHelper} the current helper. + */ + on : function (evt, fn) { + var self = this; + + if(evt === "data") { + this._worker.on(evt, function (chunk) { + fn.call(self, chunk.data, chunk.meta); + }); + } else { + this._worker.on(evt, function () { + utils.delay(fn, arguments, self); + }); + } + return this; + }, + /** + * Resume the flow of chunks. + * @return {StreamHelper} the current helper. + */ + resume : function () { + utils.delay(this._worker.resume, [], this._worker); + return this; + }, + /** + * Pause the flow of chunks. + * @return {StreamHelper} the current helper. + */ + pause : function () { + this._worker.pause(); + return this; + }, + /** + * Return a nodejs stream for this helper. + * @param {Function} updateCb the update callback. + * @return {NodejsStreamOutputAdapter} the nodejs stream. + */ + toNodejsStream : function (updateCb) { + utils.checkSupport("nodestream"); + if (this._outputType !== "nodebuffer") { + // an object stream containing blob/arraybuffer/uint8array/string + // is strange and I don't know if it would be useful. + // I you find this comment and have a good usecase, please open a + // bug report ! + throw new Error(this._outputType + " is not supported by this method"); + } + + return new NodejsStreamOutputAdapter(this, { + objectMode : this._outputType !== "nodebuffer" + }, updateCb); + } +}; + + +module.exports = StreamHelper; + +},{"../base64":1,"../external":6,"../nodejs/NodejsStreamOutputAdapter":13,"../support":30,"../utils":32,"./ConvertWorker":24,"./GenericWorker":28}],30:[function(require,module,exports){ +'use strict'; + +exports.base64 = true; +exports.array = true; +exports.string = true; +exports.arraybuffer = typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined"; +exports.nodebuffer = typeof Buffer !== "undefined"; +// contains true if JSZip can read/generate Uint8Array, false otherwise. +exports.uint8array = typeof Uint8Array !== "undefined"; + +if (typeof ArrayBuffer === "undefined") { + exports.blob = false; +} +else { + var buffer = new ArrayBuffer(0); + try { + exports.blob = new Blob([buffer], { + type: "application/zip" + }).size === 0; + } + catch (e) { + try { + var Builder = self.BlobBuilder || self.WebKitBlobBuilder || self.MozBlobBuilder || self.MSBlobBuilder; + var builder = new Builder(); + builder.append(buffer); + exports.blob = builder.getBlob('application/zip').size === 0; + } + catch (e) { + exports.blob = false; + } + } +} + +try { + exports.nodestream = !!require('readable-stream').Readable; +} catch(e) { + exports.nodestream = false; +} + +},{"readable-stream":16}],31:[function(require,module,exports){ +'use strict'; + +var utils = require('./utils'); +var support = require('./support'); +var nodejsUtils = require('./nodejsUtils'); +var GenericWorker = require('./stream/GenericWorker'); + +/** + * The following functions come from pako, from pako/lib/utils/strings + * released under the MIT license, see pako https://github.com/nodeca/pako/ + */ + +// Table with utf8 lengths (calculated by first byte of sequence) +// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS, +// because max possible codepoint is 0x10ffff +var _utf8len = new Array(256); +for (var i=0; i<256; i++) { + _utf8len[i] = (i >= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1); +} +_utf8len[254]=_utf8len[254]=1; // Invalid sequence start + +// convert string to array (typed, when possible) +var string2buf = function (str) { + var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; + + // count binary size + for (m_pos = 0; m_pos < str_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { + c2 = str.charCodeAt(m_pos+1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; + } + + // allocate buffer + if (support.uint8array) { + buf = new Uint8Array(buf_len); + } else { + buf = new Array(buf_len); + } + + // convert + for (i=0, m_pos = 0; i < buf_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { + c2 = str.charCodeAt(m_pos+1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + if (c < 0x80) { + /* one byte */ + buf[i++] = c; + } else if (c < 0x800) { + /* two bytes */ + buf[i++] = 0xC0 | (c >>> 6); + buf[i++] = 0x80 | (c & 0x3f); + } else if (c < 0x10000) { + /* three bytes */ + buf[i++] = 0xE0 | (c >>> 12); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } else { + /* four bytes */ + buf[i++] = 0xf0 | (c >>> 18); + buf[i++] = 0x80 | (c >>> 12 & 0x3f); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } + } + + return buf; +}; + +// Calculate max possible position in utf8 buffer, +// that will not break sequence. If that's not possible +// - (very small limits) return max size as is. +// +// buf[] - utf8 bytes array +// max - length limit (mandatory); +var utf8border = function(buf, max) { + var pos; + + max = max || buf.length; + if (max > buf.length) { max = buf.length; } + + // go back from last position, until start of sequence found + pos = max-1; + while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } + + // Fuckup - very small and broken sequence, + // return max, because we should return something anyway. + if (pos < 0) { return max; } + + // If we came to start of buffer - that means vuffer is too small, + // return max too. + if (pos === 0) { return max; } + + return (pos + _utf8len[buf[pos]] > max) ? pos : max; +}; + +// convert array to string +var buf2string = function (buf) { + var str, i, out, c, c_len; + var len = buf.length; + + // Reserve max possible length (2 words per char) + // NB: by unknown reasons, Array is significantly faster for + // String.fromCharCode.apply than Uint16Array. + var utf16buf = new Array(len*2); + + for (out=0, i=0; i 4) { utf16buf[out++] = 0xfffd; i += c_len-1; continue; } + + // apply mask on first byte + c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; + // join the rest + while (c_len > 1 && i < len) { + c = (c << 6) | (buf[i++] & 0x3f); + c_len--; + } + + // terminated by end of string? + if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } + + if (c < 0x10000) { + utf16buf[out++] = c; + } else { + c -= 0x10000; + utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); + utf16buf[out++] = 0xdc00 | (c & 0x3ff); + } + } + + // shrinkBuf(utf16buf, out) + if (utf16buf.length !== out) { + if(utf16buf.subarray) { + utf16buf = utf16buf.subarray(0, out); + } else { + utf16buf.length = out; + } + } + + // return String.fromCharCode.apply(null, utf16buf); + return utils.applyFromCharCode(utf16buf); +}; + + +// That's all for the pako functions. + + +/** + * Transform a javascript string into an array (typed if possible) of bytes, + * UTF-8 encoded. + * @param {String} str the string to encode + * @return {Array|Uint8Array|Buffer} the UTF-8 encoded string. + */ +exports.utf8encode = function utf8encode(str) { + if (support.nodebuffer) { + return nodejsUtils.newBufferFrom(str, "utf-8"); + } + + return string2buf(str); +}; + + +/** + * Transform a bytes array (or a representation) representing an UTF-8 encoded + * string into a javascript string. + * @param {Array|Uint8Array|Buffer} buf the data de decode + * @return {String} the decoded string. + */ +exports.utf8decode = function utf8decode(buf) { + if (support.nodebuffer) { + return utils.transformTo("nodebuffer", buf).toString("utf-8"); + } + + buf = utils.transformTo(support.uint8array ? "uint8array" : "array", buf); + + return buf2string(buf); +}; + +/** + * A worker to decode utf8 encoded binary chunks into string chunks. + * @constructor + */ +function Utf8DecodeWorker() { + GenericWorker.call(this, "utf-8 decode"); + // the last bytes if a chunk didn't end with a complete codepoint. + this.leftOver = null; +} +utils.inherits(Utf8DecodeWorker, GenericWorker); + +/** + * @see GenericWorker.processChunk + */ +Utf8DecodeWorker.prototype.processChunk = function (chunk) { + + var data = utils.transformTo(support.uint8array ? "uint8array" : "array", chunk.data); + + // 1st step, re-use what's left of the previous chunk + if (this.leftOver && this.leftOver.length) { + if(support.uint8array) { + var previousData = data; + data = new Uint8Array(previousData.length + this.leftOver.length); + data.set(this.leftOver, 0); + data.set(previousData, this.leftOver.length); + } else { + data = this.leftOver.concat(data); + } + this.leftOver = null; + } + + var nextBoundary = utf8border(data); + var usableData = data; + if (nextBoundary !== data.length) { + if (support.uint8array) { + usableData = data.subarray(0, nextBoundary); + this.leftOver = data.subarray(nextBoundary, data.length); + } else { + usableData = data.slice(0, nextBoundary); + this.leftOver = data.slice(nextBoundary, data.length); + } + } + + this.push({ + data : exports.utf8decode(usableData), + meta : chunk.meta + }); +}; + +/** + * @see GenericWorker.flush + */ +Utf8DecodeWorker.prototype.flush = function () { + if(this.leftOver && this.leftOver.length) { + this.push({ + data : exports.utf8decode(this.leftOver), + meta : {} + }); + this.leftOver = null; + } +}; +exports.Utf8DecodeWorker = Utf8DecodeWorker; + +/** + * A worker to endcode string chunks into utf8 encoded binary chunks. + * @constructor + */ +function Utf8EncodeWorker() { + GenericWorker.call(this, "utf-8 encode"); +} +utils.inherits(Utf8EncodeWorker, GenericWorker); + +/** + * @see GenericWorker.processChunk + */ +Utf8EncodeWorker.prototype.processChunk = function (chunk) { + this.push({ + data : exports.utf8encode(chunk.data), + meta : chunk.meta + }); +}; +exports.Utf8EncodeWorker = Utf8EncodeWorker; + +},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(require,module,exports){ +'use strict'; + +var support = require('./support'); +var base64 = require('./base64'); +var nodejsUtils = require('./nodejsUtils'); +var setImmediate = require('set-immediate-shim'); +var external = require("./external"); + + +/** + * Convert a string that pass as a "binary string": it should represent a byte + * array but may have > 255 char codes. Be sure to take only the first byte + * and returns the byte array. + * @param {String} str the string to transform. + * @return {Array|Uint8Array} the string in a binary format. + */ +function string2binary(str) { + var result = null; + if (support.uint8array) { + result = new Uint8Array(str.length); + } else { + result = new Array(str.length); + } + return stringToArrayLike(str, result); +} + +/** + * Create a new blob with the given content and the given type. + * @param {String|ArrayBuffer} part the content to put in the blob. DO NOT use + * an Uint8Array because the stock browser of android 4 won't accept it (it + * will be silently converted to a string, "[object Uint8Array]"). + * + * Use only ONE part to build the blob to avoid a memory leak in IE11 / Edge: + * when a large amount of Array is used to create the Blob, the amount of + * memory consumed is nearly 100 times the original data amount. + * + * @param {String} type the mime type of the blob. + * @return {Blob} the created blob. + */ +exports.newBlob = function(part, type) { + exports.checkSupport("blob"); + + try { + // Blob constructor + return new Blob([part], { + type: type + }); + } + catch (e) { + + try { + // deprecated, browser only, old way + var Builder = self.BlobBuilder || self.WebKitBlobBuilder || self.MozBlobBuilder || self.MSBlobBuilder; + var builder = new Builder(); + builder.append(part); + return builder.getBlob(type); + } + catch (e) { + + // well, fuck ?! + throw new Error("Bug : can't construct the Blob."); + } + } + + +}; +/** + * The identity function. + * @param {Object} input the input. + * @return {Object} the same input. + */ +function identity(input) { + return input; +} + +/** + * Fill in an array with a string. + * @param {String} str the string to use. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated). + * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array. + */ +function stringToArrayLike(str, array) { + for (var i = 0; i < str.length; ++i) { + array[i] = str.charCodeAt(i) & 0xFF; + } + return array; +} + +/** + * An helper for the function arrayLikeToString. + * This contains static information and functions that + * can be optimized by the browser JIT compiler. + */ +var arrayToStringHelper = { + /** + * Transform an array of int into a string, chunk by chunk. + * See the performances notes on arrayLikeToString. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform. + * @param {String} type the type of the array. + * @param {Integer} chunk the chunk size. + * @return {String} the resulting string. + * @throws Error if the chunk is too big for the stack. + */ + stringifyByChunk: function(array, type, chunk) { + var result = [], k = 0, len = array.length; + // shortcut + if (len <= chunk) { + return String.fromCharCode.apply(null, array); + } + while (k < len) { + if (type === "array" || type === "nodebuffer") { + result.push(String.fromCharCode.apply(null, array.slice(k, Math.min(k + chunk, len)))); + } + else { + result.push(String.fromCharCode.apply(null, array.subarray(k, Math.min(k + chunk, len)))); + } + k += chunk; + } + return result.join(""); + }, + /** + * Call String.fromCharCode on every item in the array. + * This is the naive implementation, which generate A LOT of intermediate string. + * This should be used when everything else fail. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform. + * @return {String} the result. + */ + stringifyByChar: function(array){ + var resultStr = ""; + for(var i = 0; i < array.length; i++) { + resultStr += String.fromCharCode(array[i]); + } + return resultStr; + }, + applyCanBeUsed : { + /** + * true if the browser accepts to use String.fromCharCode on Uint8Array + */ + uint8array : (function () { + try { + return support.uint8array && String.fromCharCode.apply(null, new Uint8Array(1)).length === 1; + } catch (e) { + return false; + } + })(), + /** + * true if the browser accepts to use String.fromCharCode on nodejs Buffer. + */ + nodebuffer : (function () { + try { + return support.nodebuffer && String.fromCharCode.apply(null, nodejsUtils.allocBuffer(1)).length === 1; + } catch (e) { + return false; + } + })() + } +}; + +/** + * Transform an array-like object to a string. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform. + * @return {String} the result. + */ +function arrayLikeToString(array) { + // Performances notes : + // -------------------- + // String.fromCharCode.apply(null, array) is the fastest, see + // see http://jsperf.com/converting-a-uint8array-to-a-string/2 + // but the stack is limited (and we can get huge arrays !). + // + // result += String.fromCharCode(array[i]); generate too many strings ! + // + // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2 + // TODO : we now have workers that split the work. Do we still need that ? + var chunk = 65536, + type = exports.getTypeOf(array), + canUseApply = true; + if (type === "uint8array") { + canUseApply = arrayToStringHelper.applyCanBeUsed.uint8array; + } else if (type === "nodebuffer") { + canUseApply = arrayToStringHelper.applyCanBeUsed.nodebuffer; + } + + if (canUseApply) { + while (chunk > 1) { + try { + return arrayToStringHelper.stringifyByChunk(array, type, chunk); + } catch (e) { + chunk = Math.floor(chunk / 2); + } + } + } + + // no apply or chunk error : slow and painful algorithm + // default browser on android 4.* + return arrayToStringHelper.stringifyByChar(array); +} + +exports.applyFromCharCode = arrayLikeToString; + + +/** + * Copy the data from an array-like to an other array-like. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated. + * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array. + */ +function arrayLikeToArrayLike(arrayFrom, arrayTo) { + for (var i = 0; i < arrayFrom.length; i++) { + arrayTo[i] = arrayFrom[i]; + } + return arrayTo; +} + +// a matrix containing functions to transform everything into everything. +var transform = {}; + +// string to ? +transform["string"] = { + "string": identity, + "array": function(input) { + return stringToArrayLike(input, new Array(input.length)); + }, + "arraybuffer": function(input) { + return transform["string"]["uint8array"](input).buffer; + }, + "uint8array": function(input) { + return stringToArrayLike(input, new Uint8Array(input.length)); + }, + "nodebuffer": function(input) { + return stringToArrayLike(input, nodejsUtils.allocBuffer(input.length)); + } +}; + +// array to ? +transform["array"] = { + "string": arrayLikeToString, + "array": identity, + "arraybuffer": function(input) { + return (new Uint8Array(input)).buffer; + }, + "uint8array": function(input) { + return new Uint8Array(input); + }, + "nodebuffer": function(input) { + return nodejsUtils.newBufferFrom(input); + } +}; + +// arraybuffer to ? +transform["arraybuffer"] = { + "string": function(input) { + return arrayLikeToString(new Uint8Array(input)); + }, + "array": function(input) { + return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength)); + }, + "arraybuffer": identity, + "uint8array": function(input) { + return new Uint8Array(input); + }, + "nodebuffer": function(input) { + return nodejsUtils.newBufferFrom(new Uint8Array(input)); + } +}; + +// uint8array to ? +transform["uint8array"] = { + "string": arrayLikeToString, + "array": function(input) { + return arrayLikeToArrayLike(input, new Array(input.length)); + }, + "arraybuffer": function(input) { + return input.buffer; + }, + "uint8array": identity, + "nodebuffer": function(input) { + return nodejsUtils.newBufferFrom(input); + } +}; + +// nodebuffer to ? +transform["nodebuffer"] = { + "string": arrayLikeToString, + "array": function(input) { + return arrayLikeToArrayLike(input, new Array(input.length)); + }, + "arraybuffer": function(input) { + return transform["nodebuffer"]["uint8array"](input).buffer; + }, + "uint8array": function(input) { + return arrayLikeToArrayLike(input, new Uint8Array(input.length)); + }, + "nodebuffer": identity +}; + +/** + * Transform an input into any type. + * The supported output type are : string, array, uint8array, arraybuffer, nodebuffer. + * If no output type is specified, the unmodified input will be returned. + * @param {String} outputType the output type. + * @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert. + * @throws {Error} an Error if the browser doesn't support the requested output type. + */ +exports.transformTo = function(outputType, input) { + if (!input) { + // undefined, null, etc + // an empty string won't harm. + input = ""; + } + if (!outputType) { + return input; + } + exports.checkSupport(outputType); + var inputType = exports.getTypeOf(input); + var result = transform[inputType][outputType](input); + return result; +}; + +/** + * Return the type of the input. + * The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer. + * @param {Object} input the input to identify. + * @return {String} the (lowercase) type of the input. + */ +exports.getTypeOf = function(input) { + if (typeof input === "string") { + return "string"; + } + if (Object.prototype.toString.call(input) === "[object Array]") { + return "array"; + } + if (support.nodebuffer && nodejsUtils.isBuffer(input)) { + return "nodebuffer"; + } + if (support.uint8array && input instanceof Uint8Array) { + return "uint8array"; + } + if (support.arraybuffer && input instanceof ArrayBuffer) { + return "arraybuffer"; + } +}; + +/** + * Throw an exception if the type is not supported. + * @param {String} type the type to check. + * @throws {Error} an Error if the browser doesn't support the requested type. + */ +exports.checkSupport = function(type) { + var supported = support[type.toLowerCase()]; + if (!supported) { + throw new Error(type + " is not supported by this platform"); + } +}; + +exports.MAX_VALUE_16BITS = 65535; +exports.MAX_VALUE_32BITS = -1; // well, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" is parsed as -1 + +/** + * Prettify a string read as binary. + * @param {string} str the string to prettify. + * @return {string} a pretty string. + */ +exports.pretty = function(str) { + var res = '', + code, i; + for (i = 0; i < (str || "").length; i++) { + code = str.charCodeAt(i); + res += '\\x' + (code < 16 ? "0" : "") + code.toString(16).toUpperCase(); + } + return res; +}; + +/** + * Defer the call of a function. + * @param {Function} callback the function to call asynchronously. + * @param {Array} args the arguments to give to the callback. + */ +exports.delay = function(callback, args, self) { + setImmediate(function () { + callback.apply(self || null, args || []); + }); +}; + +/** + * Extends a prototype with an other, without calling a constructor with + * side effects. Inspired by nodejs' `utils.inherits` + * @param {Function} ctor the constructor to augment + * @param {Function} superCtor the parent constructor to use + */ +exports.inherits = function (ctor, superCtor) { + var Obj = function() {}; + Obj.prototype = superCtor.prototype; + ctor.prototype = new Obj(); +}; + +/** + * Merge the objects passed as parameters into a new one. + * @private + * @param {...Object} var_args All objects to merge. + * @return {Object} a new object with the data of the others. + */ +exports.extend = function() { + var result = {}, i, attr; + for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers + for (attr in arguments[i]) { + if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") { + result[attr] = arguments[i][attr]; + } + } + } + return result; +}; + +/** + * Transform arbitrary content into a Promise. + * @param {String} name a name for the content being processed. + * @param {Object} inputData the content to process. + * @param {Boolean} isBinary true if the content is not an unicode string + * @param {Boolean} isOptimizedBinaryString true if the string content only has one byte per character. + * @param {Boolean} isBase64 true if the string content is encoded with base64. + * @return {Promise} a promise in a format usable by JSZip. + */ +exports.prepareContent = function(name, inputData, isBinary, isOptimizedBinaryString, isBase64) { + + // if inputData is already a promise, this flatten it. + var promise = external.Promise.resolve(inputData).then(function(data) { + + + var isBlob = support.blob && (data instanceof Blob || ['[object File]', '[object Blob]'].indexOf(Object.prototype.toString.call(data)) !== -1); + + if (isBlob && typeof FileReader !== "undefined") { + return new external.Promise(function (resolve, reject) { + var reader = new FileReader(); + + reader.onload = function(e) { + resolve(e.target.result); + }; + reader.onerror = function(e) { + reject(e.target.error); + }; + reader.readAsArrayBuffer(data); + }); + } else { + return data; + } + }); + + return promise.then(function(data) { + var dataType = exports.getTypeOf(data); + + if (!dataType) { + return external.Promise.reject( + new Error("Can't read the data of '" + name + "'. Is it " + + "in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?") + ); + } + // special case : it's way easier to work with Uint8Array than with ArrayBuffer + if (dataType === "arraybuffer") { + data = exports.transformTo("uint8array", data); + } else if (dataType === "string") { + if (isBase64) { + data = base64.decode(data); + } + else if (isBinary) { + // optimizedBinaryString === true means that the file has already been filtered with a 0xFF mask + if (isOptimizedBinaryString !== true) { + // this is a string, not in a base64 format. + // Be sure that this is a correct "binary string" + data = string2binary(data); + } + } + } + return data; + }); +}; + +},{"./base64":1,"./external":6,"./nodejsUtils":14,"./support":30,"set-immediate-shim":54}],33:[function(require,module,exports){ +'use strict'; +var readerFor = require('./reader/readerFor'); +var utils = require('./utils'); +var sig = require('./signature'); +var ZipEntry = require('./zipEntry'); +var utf8 = require('./utf8'); +var support = require('./support'); +// class ZipEntries {{{ +/** + * All the entries in the zip file. + * @constructor + * @param {Object} loadOptions Options for loading the stream. + */ +function ZipEntries(loadOptions) { + this.files = []; + this.loadOptions = loadOptions; +} +ZipEntries.prototype = { + /** + * Check that the reader is on the specified signature. + * @param {string} expectedSignature the expected signature. + * @throws {Error} if it is an other signature. + */ + checkSignature: function(expectedSignature) { + if (!this.reader.readAndCheckSignature(expectedSignature)) { + this.reader.index -= 4; + var signature = this.reader.readString(4); + throw new Error("Corrupted zip or bug: unexpected signature " + "(" + utils.pretty(signature) + ", expected " + utils.pretty(expectedSignature) + ")"); + } + }, + /** + * Check if the given signature is at the given index. + * @param {number} askedIndex the index to check. + * @param {string} expectedSignature the signature to expect. + * @return {boolean} true if the signature is here, false otherwise. + */ + isSignature: function(askedIndex, expectedSignature) { + var currentIndex = this.reader.index; + this.reader.setIndex(askedIndex); + var signature = this.reader.readString(4); + var result = signature === expectedSignature; + this.reader.setIndex(currentIndex); + return result; + }, + /** + * Read the end of the central directory. + */ + readBlockEndOfCentral: function() { + this.diskNumber = this.reader.readInt(2); + this.diskWithCentralDirStart = this.reader.readInt(2); + this.centralDirRecordsOnThisDisk = this.reader.readInt(2); + this.centralDirRecords = this.reader.readInt(2); + this.centralDirSize = this.reader.readInt(4); + this.centralDirOffset = this.reader.readInt(4); + + this.zipCommentLength = this.reader.readInt(2); + // warning : the encoding depends of the system locale + // On a linux machine with LANG=en_US.utf8, this field is utf8 encoded. + // On a windows machine, this field is encoded with the localized windows code page. + var zipComment = this.reader.readData(this.zipCommentLength); + var decodeParamType = support.uint8array ? "uint8array" : "array"; + // To get consistent behavior with the generation part, we will assume that + // this is utf8 encoded unless specified otherwise. + var decodeContent = utils.transformTo(decodeParamType, zipComment); + this.zipComment = this.loadOptions.decodeFileName(decodeContent); + }, + /** + * Read the end of the Zip 64 central directory. + * Not merged with the method readEndOfCentral : + * The end of central can coexist with its Zip64 brother, + * I don't want to read the wrong number of bytes ! + */ + readBlockZip64EndOfCentral: function() { + this.zip64EndOfCentralSize = this.reader.readInt(8); + this.reader.skip(4); + // this.versionMadeBy = this.reader.readString(2); + // this.versionNeeded = this.reader.readInt(2); + this.diskNumber = this.reader.readInt(4); + this.diskWithCentralDirStart = this.reader.readInt(4); + this.centralDirRecordsOnThisDisk = this.reader.readInt(8); + this.centralDirRecords = this.reader.readInt(8); + this.centralDirSize = this.reader.readInt(8); + this.centralDirOffset = this.reader.readInt(8); + + this.zip64ExtensibleData = {}; + var extraDataSize = this.zip64EndOfCentralSize - 44, + index = 0, + extraFieldId, + extraFieldLength, + extraFieldValue; + while (index < extraDataSize) { + extraFieldId = this.reader.readInt(2); + extraFieldLength = this.reader.readInt(4); + extraFieldValue = this.reader.readData(extraFieldLength); + this.zip64ExtensibleData[extraFieldId] = { + id: extraFieldId, + length: extraFieldLength, + value: extraFieldValue + }; + } + }, + /** + * Read the end of the Zip 64 central directory locator. + */ + readBlockZip64EndOfCentralLocator: function() { + this.diskWithZip64CentralDirStart = this.reader.readInt(4); + this.relativeOffsetEndOfZip64CentralDir = this.reader.readInt(8); + this.disksCount = this.reader.readInt(4); + if (this.disksCount > 1) { + throw new Error("Multi-volumes zip are not supported"); + } + }, + /** + * Read the local files, based on the offset read in the central part. + */ + readLocalFiles: function() { + var i, file; + for (i = 0; i < this.files.length; i++) { + file = this.files[i]; + this.reader.setIndex(file.localHeaderOffset); + this.checkSignature(sig.LOCAL_FILE_HEADER); + file.readLocalPart(this.reader); + file.handleUTF8(); + file.processAttributes(); + } + }, + /** + * Read the central directory. + */ + readCentralDir: function() { + var file; + + this.reader.setIndex(this.centralDirOffset); + while (this.reader.readAndCheckSignature(sig.CENTRAL_FILE_HEADER)) { + file = new ZipEntry({ + zip64: this.zip64 + }, this.loadOptions); + file.readCentralPart(this.reader); + this.files.push(file); + } + + if (this.centralDirRecords !== this.files.length) { + if (this.centralDirRecords !== 0 && this.files.length === 0) { + // We expected some records but couldn't find ANY. + // This is really suspicious, as if something went wrong. + throw new Error("Corrupted zip or bug: expected " + this.centralDirRecords + " records in central dir, got " + this.files.length); + } else { + // We found some records but not all. + // Something is wrong but we got something for the user: no error here. + // console.warn("expected", this.centralDirRecords, "records in central dir, got", this.files.length); + } + } + }, + /** + * Read the end of central directory. + */ + readEndOfCentral: function() { + var offset = this.reader.lastIndexOfSignature(sig.CENTRAL_DIRECTORY_END); + if (offset < 0) { + // Check if the content is a truncated zip or complete garbage. + // A "LOCAL_FILE_HEADER" is not required at the beginning (auto + // extractible zip for example) but it can give a good hint. + // If an ajax request was used without responseType, we will also + // get unreadable data. + var isGarbage = !this.isSignature(0, sig.LOCAL_FILE_HEADER); + + if (isGarbage) { + throw new Error("Can't find end of central directory : is this a zip file ? " + + "If it is, see https://stuk.github.io/jszip/documentation/howto/read_zip.html"); + } else { + throw new Error("Corrupted zip: can't find end of central directory"); + } + + } + this.reader.setIndex(offset); + var endOfCentralDirOffset = offset; + this.checkSignature(sig.CENTRAL_DIRECTORY_END); + this.readBlockEndOfCentral(); + + + /* extract from the zip spec : + 4) If one of the fields in the end of central directory + record is too small to hold required data, the field + should be set to -1 (0xFFFF or 0xFFFFFFFF) and the + ZIP64 format record should be created. + 5) The end of central directory record and the + Zip64 end of central directory locator record must + reside on the same disk when splitting or spanning + an archive. + */ + if (this.diskNumber === utils.MAX_VALUE_16BITS || this.diskWithCentralDirStart === utils.MAX_VALUE_16BITS || this.centralDirRecordsOnThisDisk === utils.MAX_VALUE_16BITS || this.centralDirRecords === utils.MAX_VALUE_16BITS || this.centralDirSize === utils.MAX_VALUE_32BITS || this.centralDirOffset === utils.MAX_VALUE_32BITS) { + this.zip64 = true; + + /* + Warning : the zip64 extension is supported, but ONLY if the 64bits integer read from + the zip file can fit into a 32bits integer. This cannot be solved : JavaScript represents + all numbers as 64-bit double precision IEEE 754 floating point numbers. + So, we have 53bits for integers and bitwise operations treat everything as 32bits. + see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Bitwise_Operators + and http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf section 8.5 + */ + + // should look for a zip64 EOCD locator + offset = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR); + if (offset < 0) { + throw new Error("Corrupted zip: can't find the ZIP64 end of central directory locator"); + } + this.reader.setIndex(offset); + this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR); + this.readBlockZip64EndOfCentralLocator(); + + // now the zip64 EOCD record + if (!this.isSignature(this.relativeOffsetEndOfZip64CentralDir, sig.ZIP64_CENTRAL_DIRECTORY_END)) { + // console.warn("ZIP64 end of central directory not where expected."); + this.relativeOffsetEndOfZip64CentralDir = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_END); + if (this.relativeOffsetEndOfZip64CentralDir < 0) { + throw new Error("Corrupted zip: can't find the ZIP64 end of central directory"); + } + } + this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir); + this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_END); + this.readBlockZip64EndOfCentral(); + } + + var expectedEndOfCentralDirOffset = this.centralDirOffset + this.centralDirSize; + if (this.zip64) { + expectedEndOfCentralDirOffset += 20; // end of central dir 64 locator + expectedEndOfCentralDirOffset += 12 /* should not include the leading 12 bytes */ + this.zip64EndOfCentralSize; + } + + var extraBytes = endOfCentralDirOffset - expectedEndOfCentralDirOffset; + + if (extraBytes > 0) { + // console.warn(extraBytes, "extra bytes at beginning or within zipfile"); + if (this.isSignature(endOfCentralDirOffset, sig.CENTRAL_FILE_HEADER)) { + // The offsets seem wrong, but we have something at the specified offset. + // So… we keep it. + } else { + // the offset is wrong, update the "zero" of the reader + // this happens if data has been prepended (crx files for example) + this.reader.zero = extraBytes; + } + } else if (extraBytes < 0) { + throw new Error("Corrupted zip: missing " + Math.abs(extraBytes) + " bytes."); + } + }, + prepareReader: function(data) { + this.reader = readerFor(data); + }, + /** + * Read a zip file and create ZipEntries. + * @param {String|ArrayBuffer|Uint8Array|Buffer} data the binary string representing a zip file. + */ + load: function(data) { + this.prepareReader(data); + this.readEndOfCentral(); + this.readCentralDir(); + this.readLocalFiles(); + } +}; +// }}} end of ZipEntries +module.exports = ZipEntries; + +},{"./reader/readerFor":22,"./signature":23,"./support":30,"./utf8":31,"./utils":32,"./zipEntry":34}],34:[function(require,module,exports){ +'use strict'; +var readerFor = require('./reader/readerFor'); +var utils = require('./utils'); +var CompressedObject = require('./compressedObject'); +var crc32fn = require('./crc32'); +var utf8 = require('./utf8'); +var compressions = require('./compressions'); +var support = require('./support'); + +var MADE_BY_DOS = 0x00; +var MADE_BY_UNIX = 0x03; + +/** + * Find a compression registered in JSZip. + * @param {string} compressionMethod the method magic to find. + * @return {Object|null} the JSZip compression object, null if none found. + */ +var findCompression = function(compressionMethod) { + for (var method in compressions) { + if (!compressions.hasOwnProperty(method)) { + continue; + } + if (compressions[method].magic === compressionMethod) { + return compressions[method]; + } + } + return null; +}; + +// class ZipEntry {{{ +/** + * An entry in the zip file. + * @constructor + * @param {Object} options Options of the current file. + * @param {Object} loadOptions Options for loading the stream. + */ +function ZipEntry(options, loadOptions) { + this.options = options; + this.loadOptions = loadOptions; +} +ZipEntry.prototype = { + /** + * say if the file is encrypted. + * @return {boolean} true if the file is encrypted, false otherwise. + */ + isEncrypted: function() { + // bit 1 is set + return (this.bitFlag & 0x0001) === 0x0001; + }, + /** + * say if the file has utf-8 filename/comment. + * @return {boolean} true if the filename/comment is in utf-8, false otherwise. + */ + useUTF8: function() { + // bit 11 is set + return (this.bitFlag & 0x0800) === 0x0800; + }, + /** + * Read the local part of a zip file and add the info in this object. + * @param {DataReader} reader the reader to use. + */ + readLocalPart: function(reader) { + var compression, localExtraFieldsLength; + + // we already know everything from the central dir ! + // If the central dir data are false, we are doomed. + // On the bright side, the local part is scary : zip64, data descriptors, both, etc. + // The less data we get here, the more reliable this should be. + // Let's skip the whole header and dash to the data ! + reader.skip(22); + // in some zip created on windows, the filename stored in the central dir contains \ instead of /. + // Strangely, the filename here is OK. + // I would love to treat these zip files as corrupted (see http://www.info-zip.org/FAQ.html#backslashes + // or APPNOTE#4.4.17.1, "All slashes MUST be forward slashes '/'") but there are a lot of bad zip generators... + // Search "unzip mismatching "local" filename continuing with "central" filename version" on + // the internet. + // + // I think I see the logic here : the central directory is used to display + // content and the local directory is used to extract the files. Mixing / and \ + // may be used to display \ to windows users and use / when extracting the files. + // Unfortunately, this lead also to some issues : http://seclists.org/fulldisclosure/2009/Sep/394 + this.fileNameLength = reader.readInt(2); + localExtraFieldsLength = reader.readInt(2); // can't be sure this will be the same as the central dir + // the fileName is stored as binary data, the handleUTF8 method will take care of the encoding. + this.fileName = reader.readData(this.fileNameLength); + reader.skip(localExtraFieldsLength); + + if (this.compressedSize === -1 || this.uncompressedSize === -1) { + throw new Error("Bug or corrupted zip : didn't get enough information from the central directory " + "(compressedSize === -1 || uncompressedSize === -1)"); + } + + compression = findCompression(this.compressionMethod); + if (compression === null) { // no compression found + throw new Error("Corrupted zip : compression " + utils.pretty(this.compressionMethod) + " unknown (inner file : " + utils.transformTo("string", this.fileName) + ")"); + } + this.decompressed = new CompressedObject(this.compressedSize, this.uncompressedSize, this.crc32, compression, reader.readData(this.compressedSize)); + }, + + /** + * Read the central part of a zip file and add the info in this object. + * @param {DataReader} reader the reader to use. + */ + readCentralPart: function(reader) { + this.versionMadeBy = reader.readInt(2); + reader.skip(2); + // this.versionNeeded = reader.readInt(2); + this.bitFlag = reader.readInt(2); + this.compressionMethod = reader.readString(2); + this.date = reader.readDate(); + this.crc32 = reader.readInt(4); + this.compressedSize = reader.readInt(4); + this.uncompressedSize = reader.readInt(4); + var fileNameLength = reader.readInt(2); + this.extraFieldsLength = reader.readInt(2); + this.fileCommentLength = reader.readInt(2); + this.diskNumberStart = reader.readInt(2); + this.internalFileAttributes = reader.readInt(2); + this.externalFileAttributes = reader.readInt(4); + this.localHeaderOffset = reader.readInt(4); + + if (this.isEncrypted()) { + throw new Error("Encrypted zip are not supported"); + } + + // will be read in the local part, see the comments there + reader.skip(fileNameLength); + this.readExtraFields(reader); + this.parseZIP64ExtraField(reader); + this.fileComment = reader.readData(this.fileCommentLength); + }, + + /** + * Parse the external file attributes and get the unix/dos permissions. + */ + processAttributes: function () { + this.unixPermissions = null; + this.dosPermissions = null; + var madeBy = this.versionMadeBy >> 8; + + // Check if we have the DOS directory flag set. + // We look for it in the DOS and UNIX permissions + // but some unknown platform could set it as a compatibility flag. + this.dir = this.externalFileAttributes & 0x0010 ? true : false; + + if(madeBy === MADE_BY_DOS) { + // first 6 bits (0 to 5) + this.dosPermissions = this.externalFileAttributes & 0x3F; + } + + if(madeBy === MADE_BY_UNIX) { + this.unixPermissions = (this.externalFileAttributes >> 16) & 0xFFFF; + // the octal permissions are in (this.unixPermissions & 0x01FF).toString(8); + } + + // fail safe : if the name ends with a / it probably means a folder + if (!this.dir && this.fileNameStr.slice(-1) === '/') { + this.dir = true; + } + }, + + /** + * Parse the ZIP64 extra field and merge the info in the current ZipEntry. + * @param {DataReader} reader the reader to use. + */ + parseZIP64ExtraField: function(reader) { + + if (!this.extraFields[0x0001]) { + return; + } + + // should be something, preparing the extra reader + var extraReader = readerFor(this.extraFields[0x0001].value); + + // I really hope that these 64bits integer can fit in 32 bits integer, because js + // won't let us have more. + if (this.uncompressedSize === utils.MAX_VALUE_32BITS) { + this.uncompressedSize = extraReader.readInt(8); + } + if (this.compressedSize === utils.MAX_VALUE_32BITS) { + this.compressedSize = extraReader.readInt(8); + } + if (this.localHeaderOffset === utils.MAX_VALUE_32BITS) { + this.localHeaderOffset = extraReader.readInt(8); + } + if (this.diskNumberStart === utils.MAX_VALUE_32BITS) { + this.diskNumberStart = extraReader.readInt(4); + } + }, + /** + * Read the central part of a zip file and add the info in this object. + * @param {DataReader} reader the reader to use. + */ + readExtraFields: function(reader) { + var end = reader.index + this.extraFieldsLength, + extraFieldId, + extraFieldLength, + extraFieldValue; + + if (!this.extraFields) { + this.extraFields = {}; + } + + while (reader.index + 4 < end) { + extraFieldId = reader.readInt(2); + extraFieldLength = reader.readInt(2); + extraFieldValue = reader.readData(extraFieldLength); + + this.extraFields[extraFieldId] = { + id: extraFieldId, + length: extraFieldLength, + value: extraFieldValue + }; + } + + reader.setIndex(end); + }, + /** + * Apply an UTF8 transformation if needed. + */ + handleUTF8: function() { + var decodeParamType = support.uint8array ? "uint8array" : "array"; + if (this.useUTF8()) { + this.fileNameStr = utf8.utf8decode(this.fileName); + this.fileCommentStr = utf8.utf8decode(this.fileComment); + } else { + var upath = this.findExtraFieldUnicodePath(); + if (upath !== null) { + this.fileNameStr = upath; + } else { + // ASCII text or unsupported code page + var fileNameByteArray = utils.transformTo(decodeParamType, this.fileName); + this.fileNameStr = this.loadOptions.decodeFileName(fileNameByteArray); + } + + var ucomment = this.findExtraFieldUnicodeComment(); + if (ucomment !== null) { + this.fileCommentStr = ucomment; + } else { + // ASCII text or unsupported code page + var commentByteArray = utils.transformTo(decodeParamType, this.fileComment); + this.fileCommentStr = this.loadOptions.decodeFileName(commentByteArray); + } + } + }, + + /** + * Find the unicode path declared in the extra field, if any. + * @return {String} the unicode path, null otherwise. + */ + findExtraFieldUnicodePath: function() { + var upathField = this.extraFields[0x7075]; + if (upathField) { + var extraReader = readerFor(upathField.value); + + // wrong version + if (extraReader.readInt(1) !== 1) { + return null; + } + + // the crc of the filename changed, this field is out of date. + if (crc32fn(this.fileName) !== extraReader.readInt(4)) { + return null; + } + + return utf8.utf8decode(extraReader.readData(upathField.length - 5)); + } + return null; + }, + + /** + * Find the unicode comment declared in the extra field, if any. + * @return {String} the unicode comment, null otherwise. + */ + findExtraFieldUnicodeComment: function() { + var ucommentField = this.extraFields[0x6375]; + if (ucommentField) { + var extraReader = readerFor(ucommentField.value); + + // wrong version + if (extraReader.readInt(1) !== 1) { + return null; + } + + // the crc of the comment changed, this field is out of date. + if (crc32fn(this.fileComment) !== extraReader.readInt(4)) { + return null; + } + + return utf8.utf8decode(extraReader.readData(ucommentField.length - 5)); + } + return null; + } +}; +module.exports = ZipEntry; + +},{"./compressedObject":2,"./compressions":3,"./crc32":4,"./reader/readerFor":22,"./support":30,"./utf8":31,"./utils":32}],35:[function(require,module,exports){ +'use strict'; + +var StreamHelper = require('./stream/StreamHelper'); +var DataWorker = require('./stream/DataWorker'); +var utf8 = require('./utf8'); +var CompressedObject = require('./compressedObject'); +var GenericWorker = require('./stream/GenericWorker'); + +/** + * A simple object representing a file in the zip file. + * @constructor + * @param {string} name the name of the file + * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data + * @param {Object} options the options of the file + */ +var ZipObject = function(name, data, options) { + this.name = name; + this.dir = options.dir; + this.date = options.date; + this.comment = options.comment; + this.unixPermissions = options.unixPermissions; + this.dosPermissions = options.dosPermissions; + + this._data = data; + this._dataBinary = options.binary; + // keep only the compression + this.options = { + compression : options.compression, + compressionOptions : options.compressionOptions + }; +}; + +ZipObject.prototype = { + /** + * Create an internal stream for the content of this object. + * @param {String} type the type of each chunk. + * @return StreamHelper the stream. + */ + internalStream: function (type) { + var result = null, outputType = "string"; + try { + if (!type) { + throw new Error("No output type specified."); + } + outputType = type.toLowerCase(); + var askUnicodeString = outputType === "string" || outputType === "text"; + if (outputType === "binarystring" || outputType === "text") { + outputType = "string"; + } + result = this._decompressWorker(); + + var isUnicodeString = !this._dataBinary; + + if (isUnicodeString && !askUnicodeString) { + result = result.pipe(new utf8.Utf8EncodeWorker()); + } + if (!isUnicodeString && askUnicodeString) { + result = result.pipe(new utf8.Utf8DecodeWorker()); + } + } catch (e) { + result = new GenericWorker("error"); + result.error(e); + } + + return new StreamHelper(result, outputType, ""); + }, + + /** + * Prepare the content in the asked type. + * @param {String} type the type of the result. + * @param {Function} onUpdate a function to call on each internal update. + * @return Promise the promise of the result. + */ + async: function (type, onUpdate) { + return this.internalStream(type).accumulate(onUpdate); + }, + + /** + * Prepare the content as a nodejs stream. + * @param {String} type the type of each chunk. + * @param {Function} onUpdate a function to call on each internal update. + * @return Stream the stream. + */ + nodeStream: function (type, onUpdate) { + return this.internalStream(type || "nodebuffer").toNodejsStream(onUpdate); + }, + + /** + * Return a worker for the compressed content. + * @private + * @param {Object} compression the compression object to use. + * @param {Object} compressionOptions the options to use when compressing. + * @return Worker the worker. + */ + _compressWorker: function (compression, compressionOptions) { + if ( + this._data instanceof CompressedObject && + this._data.compression.magic === compression.magic + ) { + return this._data.getCompressedWorker(); + } else { + var result = this._decompressWorker(); + if(!this._dataBinary) { + result = result.pipe(new utf8.Utf8EncodeWorker()); + } + return CompressedObject.createWorkerFrom(result, compression, compressionOptions); + } + }, + /** + * Return a worker for the decompressed content. + * @private + * @return Worker the worker. + */ + _decompressWorker : function () { + if (this._data instanceof CompressedObject) { + return this._data.getContentWorker(); + } else if (this._data instanceof GenericWorker) { + return this._data; + } else { + return new DataWorker(this._data); + } + } +}; + +var removedMethods = ["asText", "asBinary", "asNodeBuffer", "asUint8Array", "asArrayBuffer"]; +var removedFn = function () { + throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide."); +}; + +for(var i = 0; i < removedMethods.length; i++) { + ZipObject.prototype[removedMethods[i]] = removedFn; +} +module.exports = ZipObject; + +},{"./compressedObject":2,"./stream/DataWorker":27,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31}],36:[function(require,module,exports){ +(function (global){ +'use strict'; +var Mutation = global.MutationObserver || global.WebKitMutationObserver; + +var scheduleDrain; + +{ + if (Mutation) { + var called = 0; + var observer = new Mutation(nextTick); + var element = global.document.createTextNode(''); + observer.observe(element, { + characterData: true + }); + scheduleDrain = function () { + element.data = (called = ++called % 2); + }; + } else if (!global.setImmediate && typeof global.MessageChannel !== 'undefined') { + var channel = new global.MessageChannel(); + channel.port1.onmessage = nextTick; + scheduleDrain = function () { + channel.port2.postMessage(0); + }; + } else if ('document' in global && 'onreadystatechange' in global.document.createElement('script')) { + scheduleDrain = function () { + + // Create a + + + + + + + + + +
+ +
+ +
+
+ +

Class Author.Builder

+
+
+ +
+
    +
  • +
    +
    Enclosing class:
    +
    Author
    +
    +
    +
    public static final class Author.Builder
    +extends Object
    +
    A builder for an author. +

    + Note: all methods throw a NullPointerException when null + is passed to them, and all properties must be set.

    +
  • +
+
+
+ +
+
+ +
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Author.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Author.html new file mode 100644 index 0000000..6bcf4d2 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Author.html @@ -0,0 +1,375 @@ + + + + + +Author (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class Author

+
+
+ +
+
    +
  • +
    +
    public final class Author
    +extends Object
    +
    An author of a certain extraction plugin. +

    + Example for creating an author: +

    + 
    + Author.builder()
    +     .name("John")
    +     .email("johndoe@email.com")
    +     .organisation("Netherlands Forensic Institute")
    +     .build();
    + 
    + 
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        builder

        +
        public static Author.Builder builder()
        +
        Start creating a new author.
        +
        +
        Returns:
        +
        a builder for an author
        +
        +
      • +
      + + + +
        +
      • +

        name

        +
        public String name()
        +
        Get the name of the author.
        +
        +
        Returns:
        +
        the name of the author
        +
        +
      • +
      + + + +
        +
      • +

        email

        +
        public String email()
        +
        Get the email address of the author.
        +
        +
        Returns:
        +
        the email address of the author.
        +
        +
      • +
      + + + +
        +
      • +

        organisation

        +
        public String organisation()
        +
        Get the name of the organisation the author belongs to.
        +
        +
        Returns:
        +
        the organisation of the author
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/BaseExtractionPlugin.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/BaseExtractionPlugin.html new file mode 100644 index 0000000..dd9b98a --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/BaseExtractionPlugin.html @@ -0,0 +1,272 @@ + + + + + +BaseExtractionPlugin (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface BaseExtractionPlugin

+
+
+
+
    +
  • +
    +
    All Known Subinterfaces:
    +
    DeferredExtractionPlugin, ExtractionPlugin
    +
    +
    +
    All Known Implementing Classes:
    +
    MetaExtractionPlugin
    +
    +
    +
    public interface BaseExtractionPlugin
    +
    This the base class for types of Extraction Plugins, and cannot be used solely as a superclass for a plugin. + Implement one of its subclasses that have a 'process' method. + Extraction plugins can be used by Hansken to process data during the extraction process.
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        pluginInfo

        +
        PluginInfo pluginInfo()
        +
        Get the information of this plugin, such as the author or a description, and the + types of traces and data it matches on.
        +
        +
        Returns:
        +
        the metadata of the plugin
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/BatchSearchResult.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/BatchSearchResult.html new file mode 100644 index 0000000..a188948 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/BatchSearchResult.html @@ -0,0 +1,369 @@ + + + + + +BatchSearchResult (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class BatchSearchResult

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.hansken.plugin.extraction.api.BatchSearchResult
    • +
    +
  • +
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        BatchSearchResult

        +
        public BatchSearchResult​(long totalResults)
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Detail

      + + + + + + + +
        +
      • +

        setTraces

        +
        public void setTraces​(SearchTrace[] traces)
        +
        Sets all traces that can be returned by calling getTraces.
        +
        +
        Parameters:
        +
        traces - an array of ImmutableTraces.
        +
        +
      • +
      + + + +
        +
      • +

        getTotalHits

        +
        public long getTotalHits()
        +
        Description copied from interface: SearchResult
        +
        Returns the total number of traces matching the query.
        +
        +
        Specified by:
        +
        getTotalHits in interface SearchResult
        +
        Returns:
        +
        total number of matching traces
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/DataContext.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/DataContext.html new file mode 100644 index 0000000..3670666 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/DataContext.html @@ -0,0 +1,291 @@ + + + + + +DataContext (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface DataContext

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        dataType

        +
        String dataType()
        +
        The type of data (see data()) that is being processed. The type is never empty or null. + This is the type of data the given data sequence represents, for example "raw" for raw + bytes or "plain" for plaintext. +

        + The data type will be one of those defined on the trace model associated with the trace being processed.

        +
        +
        Returns:
        +
        the name of the data type
        +
        +
      • +
      + + + +
        +
      • +

        data

        +
        RandomAccessData data()
        +
        A data sequence belonging to the trace currently being extracted. If necessary, + the type of the underlying data can be requested using dataType().
        +
        +
        Returns:
        +
        the data sequence
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/DataWriter.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/DataWriter.html new file mode 100644 index 0000000..20af3a9 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/DataWriter.html @@ -0,0 +1,268 @@ + + + + + +DataWriter (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface DataWriter

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        writeTo

        +
        void writeTo​(OutputStream stream)
        +      throws IOException
        +
        Write data to given OutputStream. +

        + Note: the received output stream should not be closed by the user. It should + also only be used within the scope of the function, other usage may be guarded against or otherwise + result in undefined behaviour.

        +
        +
        Parameters:
        +
        stream - the stream to write data to
        +
        Throws:
        +
        IOException - when an I/O error occurs
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/DeferredExtractionPlugin.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/DeferredExtractionPlugin.html new file mode 100644 index 0000000..a1d087d --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/DeferredExtractionPlugin.html @@ -0,0 +1,298 @@ + + + + + +DeferredExtractionPlugin (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface DeferredExtractionPlugin

+
+
+
+
    +
  • +
    +
    All Superinterfaces:
    +
    BaseExtractionPlugin
    +
    +
    +
    public interface DeferredExtractionPlugin
    +extends BaseExtractionPlugin
    +
    Deferred extraction plugins can be used by Hansken to process traces during the extraction process. + A processed trace can be enriched with new information and new child traces can also be created. +

    + The difference between this and a normal ExtractionPlugin is that this plugin is able to run a secondary query + for traces and combine the results with previously retrieved traces. +

    + When a plugin matches on the trace which is currently processed by Hansken + (for example, because it has certain properties), the plugin will receive the + matched trace in order to process it (see process(Trace, DataContext, TraceSearcher)).

    +
  • +
+
+
+ +
+
+
    +
  • + +
    + +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/ExtractionPlugin.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/ExtractionPlugin.html new file mode 100644 index 0000000..65e44a7 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/ExtractionPlugin.html @@ -0,0 +1,296 @@ + + + + + +ExtractionPlugin (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface ExtractionPlugin

+
+
+
+
    +
  • +
    +
    All Superinterfaces:
    +
    BaseExtractionPlugin
    +
    +
    +
    All Known Implementing Classes:
    +
    MetaExtractionPlugin
    +
    +
    +
    public interface ExtractionPlugin
    +extends BaseExtractionPlugin
    +
    Extraction plugins can be used by Hansken to process traces during the extraction process. + A processed trace can be enriched with new information and new child traces can also be created. +

    + When a plugin matches on the trace which is currently processed by Hansken + (for example, because it has certain properties or a certain type of data sequence), the plugin will receive the + matched trace and data in order to process it (see process(Trace, DataContext)).

    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        process

        +
        void process​(Trace trace,
        +             DataContext dataContext)
        +      throws IOException
        +
        Start processing a trace with a given data context. + + When processing a given trace, new properties may be set on it. New children can be added using + Trace.newChild(String, ThrowingConsumer). +

        + A trace can have multiple data sequences of different types. Because of this, a certain trace might + be processed multiple times (depending on if this plugin triggers on the different data types). +

        + Note: the given trace should only be modified within the scope of this method. + Any modifications afterwards may be guarded against or result in undefined behavior.

        +
        +
        Parameters:
        +
        trace - the trace to process
        +
        dataContext - data context of the traces data stream that is being processed
        +
        Throws:
        +
        IOException - when an I/O error occurs
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/ImmutableTrace.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/ImmutableTrace.html new file mode 100644 index 0000000..c46b255 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/ImmutableTrace.html @@ -0,0 +1,338 @@ + + + + + +ImmutableTrace (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface ImmutableTrace

+
+
+
+
    +
  • +
    +
    All Known Subinterfaces:
    +
    SearchTrace, Trace
    +
    +
    +
    public interface ImmutableTrace
    +
    A trace contains information about processed data. A trace should conform to the trace model defined by Hansken. + Unlike a Trace, an ImmutableTrace instance lacks methods to modify or update its properties and data + streams after the instance is created.
    +
  • +
+
+
+
    +
  • + +
    + +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        traceId

        +
        String traceId()
        +
        Get the trace id of this trace. + Note: This value is equal to the id of a Hansken Trace.
        +
        +
        Returns:
        +
        the id of this trace
        +
        +
      • +
      + + + +
        +
      • +

        types

        +
        Set<String> types()
        +
        Get all the types of this trace.
        +
        +
        Returns:
        +
        all types of this trace
        +
        +
      • +
      + + + +
        +
      • +

        properties

        +
        Set<String> properties()
        +
        Return the names of all the properties contained in this trace.
        +
        +
        Returns:
        +
        the properties of this trace
        +
        +
      • +
      + + + +
        +
      • +

        get

        +
        <T> T get​(String name)
        +
        Get the value of the property with given name on this trace. +

        + Note: the method is declared with a type parameter to allow for + automatic casting. It is an unchecked cast, so implementers have to make sure that + the type of the value returned is of type T.

        +
        +
        Type Parameters:
        +
        T - the type of the value
        +
        Parameters:
        +
        name - the name of the value to get
        +
        Returns:
        +
        the value of the property
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/LatLong.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/LatLong.html new file mode 100644 index 0000000..23f6792 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/LatLong.html @@ -0,0 +1,449 @@ + + + + + +LatLong (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class LatLong

+
+
+ +
+
    +
  • +
    +
    public class LatLong
    +extends Object
    +
    A geographical location consisting of a latitude and a longitude.
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        LatLong

        +
        protected LatLong​(double latitude,
        +                  double longitude)
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        of

        +
        public static LatLong of​(double latitude,
        +                         double longitude)
        +
        Create a new geographical point with given latitude + and longitude. +

        + Note: this does not do any input validation. Valid + values should be between -90 and +90 inclusive for latitude and + between -180 and +180 inclusive for longitude.

        +
        +
        Parameters:
        +
        latitude - the latitude
        +
        longitude - the longitude
        +
        Returns:
        +
        the new point
        +
        +
      • +
      + + + +
        +
      • +

        latitude

        +
        public double latitude()
        +
        The north-south position of a point on earth.
        +
        +
        Returns:
        +
        the latitude
        +
        +
      • +
      + + + +
        +
      • +

        longitude

        +
        public double longitude()
        +
        The east-west position of a point on earth.
        +
        +
        Returns:
        +
        the longitude
        +
        +
      • +
      + + + +
        +
      • +

        toISO6709

        +
        public String toISO6709()
        +
        Format to String using ISO 6709. The output is in in 5 decimals. + The decimal degree precision is 1.1132m at the equator
        +
        +
        Returns:
        +
        notation according to ISO 6709
        +
        +
      • +
      + + + +
        +
      • +

        equals

        +
        public boolean equals​(Object o)
        +
        +
        Overrides:
        +
        equals in class Object
        +
        +
      • +
      + + + +
        +
      • +

        hashCode

        +
        public int hashCode()
        +
        +
        Overrides:
        +
        hashCode in class Object
        +
        +
      • +
      + + + + +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/MaturityLevel.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/MaturityLevel.html new file mode 100644 index 0000000..8274d61 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/MaturityLevel.html @@ -0,0 +1,403 @@ + + + + + +MaturityLevel (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum MaturityLevel

+
+
+ +
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Enum Constant Detail

      + + + +
        +
      • +

        PROOF_OF_CONCEPT

        +
        public static final MaturityLevel PROOF_OF_CONCEPT
        +
        The plugin is in a proof of concept phase, not yet ready for test or production.
        +
      • +
      + + + +
        +
      • +

        READY_FOR_TEST

        +
        public static final MaturityLevel READY_FOR_TEST
        +
        The plugin can be used in a test environment and is expected to be fully functional.
        +
      • +
      + + + +
        +
      • +

        PRODUCTION_READY

        +
        public static final MaturityLevel PRODUCTION_READY
        +
        The plugin is ready to be used in a production environment.
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static MaturityLevel[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (MaturityLevel c : MaturityLevel.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static MaturityLevel valueOf​(String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        IllegalArgumentException - if this enum type has no constant with the specified name
        +
        NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/MetaExtractionPlugin.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/MetaExtractionPlugin.html new file mode 100644 index 0000000..b641ed6 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/MetaExtractionPlugin.html @@ -0,0 +1,386 @@ + + + + + +MetaExtractionPlugin (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class MetaExtractionPlugin

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.hansken.plugin.extraction.api.MetaExtractionPlugin
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    BaseExtractionPlugin, ExtractionPlugin
    +
    +
    +
    public abstract class MetaExtractionPlugin
    +extends Object
    +implements ExtractionPlugin
    +
    Meta extraction plugins can be used by Hansken to process traces during the extraction process. + A processed trace can be enriched with new information and new child traces can also be created. +

    + The difference between this and a normal ExtractionPlugin is that this plugin does not receive + or processes any data, only a trace itself. +

    + When a plugin matches on the trace which is currently processed by Hansken + (for example, because it has certain properties), the plugin will receive the + matched trace in order to process it (see process(Trace)). +

    + Note for Hansken core developers: specifying 'meta' in the matcher is + not necessary, the framework takes care of this.

    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        MetaExtractionPlugin

        +
        public MetaExtractionPlugin()
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        process

        +
        public final void process​(Trace trace,
        +                          DataContext dataContext)
        +                   throws IOException
        +
        Description copied from interface: ExtractionPlugin
        +
        Start processing a trace with a given data context. + + When processing a given trace, new properties may be set on it. New children can be added using + Trace.newChild(String, ThrowingConsumer). +

        + A trace can have multiple data sequences of different types. Because of this, a certain trace might + be processed multiple times (depending on if this plugin triggers on the different data types). +

        + Note: the given trace should only be modified within the scope of this method. + Any modifications afterwards may be guarded against or result in undefined behavior.

        +
        +
        Specified by:
        +
        process in interface ExtractionPlugin
        +
        Parameters:
        +
        trace - the trace to process
        +
        dataContext - data context of the traces data stream that is being processed
        +
        Throws:
        +
        IOException - when an I/O error occurs
        +
        +
      • +
      + + + +
        +
      • +

        process

        +
        public abstract void process​(Trace trace)
        +                      throws IOException
        +
        Start processing a trace without any of its associated data streams. + When processing a given trace, new properties may be set on it. New children can be added using + Trace.newChild(String, ThrowingConsumer). +

        + Note: the given trace should only be modified within the scope of this method. + Any modifications afterwards may be guarded against or result in undefined behaviour.

        +
        +
        Parameters:
        +
        trace - the trace to process
        +
        Throws:
        +
        IOException - when an I/O error occurs
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginId.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginId.html new file mode 100644 index 0000000..0cc1db5 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginId.html @@ -0,0 +1,374 @@ + + + + + +PluginId (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class PluginId

+
+
+ +
+
    +
  • +
    +
    public class PluginId
    +extends Object
    +
    Identifier of a plugin, consisting of domain, category and name. Needs to be unique among all tools/plugins.
    +
    +
    Author:
    +
    Netherlands Forensic Institute
    +
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        PluginId

        +
        public PluginId​(String domain,
        +                String category,
        +                String name)
        +
        Create a unique identifier for a plugin, consisting of domain, category and name.
        +
        +
        Parameters:
        +
        domain - the domain of the organisation, for example "nfi.nl"
        +
        category - the action group of the plugin, for example `extract`, `carve`, `classify` (read the SDK documentation for more details).
        +
        name - the name of the plugin, or in the classic sense, a description detailing the action(s) of the plugin. Note that the name can contain (forward) slashes.
        + example: "nfi.nl/extract/ocr/detection/plugin".
        + in this example nfi.nl is the domain, extract is the category, and ocr/detection/plugin is the name.
        +
        +
      • +
      +
    • +
    +
    + +
    + +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginInfo.Builder.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginInfo.Builder.html new file mode 100644 index 0000000..1783092 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginInfo.Builder.html @@ -0,0 +1,582 @@ + + + + + +PluginInfo.Builder (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class PluginInfo.Builder

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.hansken.plugin.extraction.api.PluginInfo.Builder
    • +
    +
  • +
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + + + + + + + + + + + + + + + + + + + +
        +
      • +

        hqlMatcher

        +
        public PluginInfo.Builder hqlMatcher​(String hqlMatcher)
        +
        Set the hqlMatcher query in string format.
        +
        +
        Parameters:
        +
        hqlMatcher - the matcher
        +
        Returns:
        +
        this
        +
        +
      • +
      + + + +
        +
      • +

        webpageUrl

        +
        public PluginInfo.Builder webpageUrl​(String webpageUrl)
        +
        Set the url to a webpage that belongs to this plugin. This can also be a link to a webpage of the git repository of the remote plugin.
        +
        +
        Parameters:
        +
        webpageUrl - url to webpage
        +
        Returns:
        +
        this
        +
        +
      • +
      + + + +
        +
      • +

        deferredIterations

        +
        public PluginInfo.Builder deferredIterations​(int deferredIterations)
        +
        Set the number of extraction iterations needed for this deferred plugin. + Only relevant for deferred plugins. + If this method is not called upon, default number of iterations will be set to 1.
        +
        +
        Parameters:
        +
        deferredIterations - number of iterations needed for this plugin. Should be between 1 and 20
        +
        Returns:
        +
        this
        +
        +
      • +
      + + + +
        +
      • +

        id

        +
        public PluginInfo.Builder id​(String domain,
        +                             String category,
        +                             String name)
        +
        Set the unique id of this plugin, consisting of domain, category and name. +

        + example: "nfi.nl/extract/ocr/detection/plugin".
        + in this example nfi.nl is the domain, extract is the category, and ocr/detection/plugin is the name.

        +
        +
        Parameters:
        +
        domain - the domain of the organisation, for example "nfi.nl"
        +
        category - the action group of the plugin, for example `extract`, `carve`, `classify` (read the SDK documentation for more details).
        +
        name - the name of the plugin, or in the classic sense, a description detailing the action(s) of the plugin. Note that the name can contain (forward) slashes.
        +
        Returns:
        +
        this
        +
        +
      • +
      + + + +
        +
      • +

        id

        +
        public PluginInfo.Builder id​(PluginId id)
        +
        Set the unique id of this plugin, consisting of domain, category and name.
        +
        +
        Parameters:
        +
        id - the unique id of this plugin, consisting of domain, category and name.
        +
        Returns:
        +
        this
        +
        +
      • +
      + + + +
        +
      • +

        license

        +
        public PluginInfo.Builder license​(String license)
        +
        Set the name of the license of this plugin.
        +
        +
        Parameters:
        +
        license - name of the license of the plugin
        +
        Returns:
        +
        this
        +
        +
      • +
      + + + +
        +
      • +

        resources

        +
        public PluginInfo.Builder resources​(PluginResources resources)
        +
        Set the resources of this plugin (optional).
        +
        +
        Parameters:
        +
        resources - the resources of this plugin
        +
        Returns:
        +
        this
        +
        +
      • +
      + + + + +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginInfo.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginInfo.html new file mode 100644 index 0000000..8a8961b --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginInfo.html @@ -0,0 +1,630 @@ + + + + + +PluginInfo (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class PluginInfo

+
+
+ +
+
    +
  • +
    +
    public final class PluginInfo
    +extends Object
    +
    Information about an extraction plugin, such as the author or + a human-readable description. +

    + Example PluginInfo: +

    +     PluginInfo.builderFor(this)
    +         .id("nfi.nl", "digest", "sha111")
    +         .pluginVersion("0.2.1")
    +         .description("Calculates hashes from data streams.")
    +         .author(Author.builder()
    +             .name("name")
    +             .email("test@email.com")
    +             .organisation("organisation")
    +             .build())
    +         .maturityLevel(MaturityLevel.PROOF_OF_CONCEPT)
    +         .hqlMatcher("hql-lite query")
    +         .webpageUrl("https://github.com/myOrg/digestPlugin")
    +         .license("Apache License 2.0")
    +         .resources(Resources.builder()
    +             .maximumCpu(1)
    +             .maximumMemory(1000)
    +             .build())
    +         .build();
    + 
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + + + +
        +
      • +

        builderFor

        +
        public static PluginInfo.Builder builderFor​(PluginType type)
        +
        Start creating new plugin information for a plugin of given type.
        +
        +
        Parameters:
        +
        type - the type of plugin to create the plugin information for
        +
        Returns:
        +
        a builder for plugin metadata
        +
        +
      • +
      + + + +
        +
      • +

        pluginType

        +
        public PluginType pluginType()
        +
        Get the type of this plugin.
        +
        +
        Returns:
        +
        the type of this plugin
        +
        +
      • +
      + + + +
        +
      • +

        name

        +
        @Deprecated
        +public String name()
        +
        Deprecated. +
        Use id() instead.
        +
        +
        Get the name of this plugin.
        +
        +
        Returns:
        +
        the name of this plugin
        +
        +
      • +
      + + + +
        +
      • +

        fullName

        +
        public String fullName()
        +
        Get the full name of this plugin, based on the id. This name will be shown in the + Hansken GUI.
        +
        +
        Returns:
        +
        the full name of this plugin, based on the id
        +
        +
      • +
      + + + +
        +
      • +

        pluginVersion

        +
        public String pluginVersion()
        +
        Get the version of this plugin.
        +
        +
        Returns:
        +
        the version of this plugin
        +
        +
      • +
      + + + +
        +
      • +

        description

        +
        public String description()
        +
        Get a human readable description of this plugin.
        +
        +
        Returns:
        +
        a description of this plugin
        +
        +
      • +
      + + + +
        +
      • +

        author

        +
        public Author author()
        +
        Get the author of this plugin.
        +
        +
        Returns:
        +
        the author of this plugin.
        +
        +
      • +
      + + + +
        +
      • +

        maturityLevel

        +
        public MaturityLevel maturityLevel()
        +
        Get the maturity level of this plugin.
        +
        +
        Returns:
        +
        the maturity level of this plugin
        +
        +
      • +
      + + + +
        +
      • +

        hqlMatcher

        +
        public String hqlMatcher()
        +
        Get the hqlMatcher of this plugin in string format.
        +
        +
        Returns:
        +
        the matcher of this plugin
        +
        +
      • +
      + + + +
        +
      • +

        webpageUrl

        +
        public String webpageUrl()
        +
        Get the url of this plugin, could point to git repo or webpage that explains the plugin.
        +
        +
        Returns:
        +
        the url of this plugin
        +
        +
      • +
      + + + +
        +
      • +

        deferredIterations

        +
        public int deferredIterations()
        +
        Get the number of extraction iterations before the deferred plugin can be applied on traces. + Only relevant for deferred plugins; default value is 1.
        +
        +
        Returns:
        +
        the number of iterations set of this plugin
        +
        +
      • +
      + + + +
        +
      • +

        id

        +
        public PluginId id()
        +
        Get the the unique id of this plugin, consisting of domain, category and name.
        +
        +
        Returns:
        +
        the url of this plugin
        +
        +
      • +
      + + + +
        +
      • +

        license

        +
        public String license()
        +
        Get the name of the license of this plugin.
        +
        +
        Returns:
        +
        the name of the license of this plugin
        +
        +
      • +
      + + + +
        +
      • +

        resources

        +
        public PluginResources resources()
        +
        Get the resources of this plugin.
        +
        +
        Returns:
        +
        the resources of this plugin
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginResources.Builder.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginResources.Builder.html new file mode 100644 index 0000000..7482ed7 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginResources.Builder.html @@ -0,0 +1,301 @@ + + + + + +PluginResources.Builder (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class PluginResources.Builder

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.hansken.plugin.extraction.api.PluginResources.Builder
    • +
    +
  • +
+
+ +
+
+ +
+
+ +
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginResources.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginResources.html new file mode 100644 index 0000000..36cfc9b --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginResources.html @@ -0,0 +1,336 @@ + + + + + +PluginResources (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class PluginResources

+
+
+ +
+
    +
  • +
    +
    public final class PluginResources
    +extends Object
    +
    PluginResources contains information about how many resources will be used for a plugin. The most common resources to specify are CPU and memory (RAM). +

    + CPU resources are measured in cpu units. One cpu is equivalent to 1 vCPU/Core for cloud providers and 1 hyperthread on bare-metal Intel processors. + Also, fractional requests are allowed. A plugin that asks 0.5 CPU uses half as much CPU as one that asks for 1 CPU. +

    + Memory resources are measured in Megabytes. +

    + Here is an example to set resources for a plugin: +

    +     Resources.builder()
    +         .maximumCpu("1.5")
    +         .maximumMemory("1024")
    +         .build();
    + 
    + In this example the plugin has a limit of 1 cpu and 1G of memory.
    +
  • +
+
+
+ +
+
+ +
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginType.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginType.html new file mode 100644 index 0000000..0f3860e --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/PluginType.html @@ -0,0 +1,403 @@ + + + + + +PluginType (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Enum PluginType

+
+
+ +
+ +
+
+ +
+
+
    +
  • + +
    + +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static PluginType[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (PluginType c : PluginType.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static PluginType valueOf​(String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        IllegalArgumentException - if this enum type has no constant with the specified name
        +
        NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ + + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/RandomAccessData.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/RandomAccessData.html new file mode 100644 index 0000000..746b903 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/RandomAccessData.html @@ -0,0 +1,477 @@ + + + + + +RandomAccessData (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface RandomAccessData

+
+
+
+
    +
  • +
    +
    All Superinterfaces:
    +
    AutoCloseable, Closeable
    +
    +
    +
    public interface RandomAccessData
    +extends Closeable
    +
    A random access readable byte sequence. It has a fixed length and an associated data type. +

    + Note: implementations are not required to implement any kind of thread safety. + It is up to the client to ensure this if necessary.

    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods Default Methods 
      Modifier and TypeMethodDescription
      longposition() +
      Get the position in the sequence.
      +
      default intread​(byte[] buffer) +
      Read bytes into the given buffer, starting at position 0 in the buffer.
      +
      default intread​(byte[] buffer, + int count) +
      Read bytes into the given buffer, starting at position 0 in the buffer.
      +
      intread​(byte[] buffer, + int offset, + int count) +
      Read data into the given buffer, starting at position offset in the buffer.
      +
      default byte[]readNBytes​(int count) +
      Read from the data sequence, returning the read bytes as an array.The data will be read from the current + position and the amount of bytes read will equal count, unless the sequence contains + fewer remaining bytes.
      +
      default longremaining() +
      Get the number of remaining bytes in this data sequence.
      +
      voidseek​(long position) +
      Move to the given absolute position in the data sequence.
      +
      longsize() +
      Get the number of bytes contained in this data sequence.
      +
      + +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        size

        +
        long size()
        +
        Get the number of bytes contained in this data sequence.
        +
        +
        Returns:
        +
        the size in bytes
        +
        +
      • +
      + + + +
        +
      • +

        position

        +
        long position()
        +
        Get the position in the sequence. The position will be a number in range of + 0 and size(), both inclusive. +

        + For example: if the size is equal to 16, the position can take any positive + value up to and including 16.

        +
        +
        Returns:
        +
        the current position
        +
        +
      • +
      + + + +
        +
      • +

        remaining

        +
        default long remaining()
        +
        Get the number of remaining bytes in this data sequence.
        +
        +
        Returns:
        +
        size() - position()
        +
        +
      • +
      + + + +
        +
      • +

        seek

        +
        void seek​(long position)
        +   throws IOException
        +
        Move to the given absolute position in the data sequence.
        +
        +
        Parameters:
        +
        position - the position to move to
        +
        Throws:
        +
        IllegalArgumentException - if given position is not in range 0 and size(), both inclusive
        +
        IOException - when an I/O error occurs
        +
        +
      • +
      + + + +
        +
      • +

        read

        +
        default int read​(byte[] buffer)
        +          throws IOException
        +
        Read bytes into the given buffer, starting at position 0 in the buffer. The data will be read from the + current position and the amount of bytes copied will equal the length of the buffer, unless + the data sequence contains fewer remaining bytes. In that case, data is read until the end of the sequence.
        +
        +
        Parameters:
        +
        buffer - the buffer to read into
        +
        Returns:
        +
        the number of bytes actually read
        +
        Throws:
        +
        IOException - when an I/O error occurs
        +
        +
      • +
      + + + +
        +
      • +

        read

        +
        default int read​(byte[] buffer,
        +                 int count)
        +          throws IOException
        +
        Read bytes into the given buffer, starting at position 0 in the buffer. The data will be read from the + current position and the amount of bytes copied will equal count, unless the data + sequence contains fewer remaining bytes. In that case, data is read until the end of the sequence.
        +
        +
        Parameters:
        +
        buffer - the buffer to read into
        +
        count - the amount of bytes to read
        +
        Returns:
        +
        the number of bytes actually read
        +
        Throws:
        +
        IllegalArgumentException - if count is negative or larger than the size of the buffer
        +
        IOException - when an I/O error occurs
        +
        +
      • +
      + + + +
        +
      • +

        read

        +
        int read​(byte[] buffer,
        +         int offset,
        +         int count)
        +  throws IOException
        +
        Read data into the given buffer, starting at position offset in the buffer. The data will be read from + the current position and the amount of bytes read will equal count, unless the + sequence contains fewer remaining bytes. In that case, data is read until the end of the sequence.
        +
        +
        Parameters:
        +
        buffer - the buffer to read into
        +
        offset - the offset in the buffer from which to start writing
        +
        count - the amount of bytes to read
        +
        Returns:
        +
        the number of bytes actually read
        +
        Throws:
        +
        IllegalArgumentException - if count or offset is negative, + or if offset + count is larger than the size of the buffer
        +
        IOException - when an I/O error occurs
        +
        +
      • +
      + + + +
        +
      • +

        readNBytes

        +
        default byte[] readNBytes​(int count)
        +                   throws IOException
        +
        Read from the data sequence, returning the read bytes as an array.The data will be read from the current + position and the amount of bytes read will equal count, unless the sequence contains + fewer remaining bytes. In that case, data is read until the end of the sequence and a smaller array is returned, + with a length equal to the number of read bytes. +

        + Note: this method will allocate a new buffer each time it is called. It is intended for simple + cases where it is convenient to read a specified number of bytes into a byte array.

        +
        +
        Parameters:
        +
        count - the amount of bytes to read
        +
        Returns:
        +
        a buffer containing the read bytes, or an empty array if we were at the end of the stream
        +
        Throws:
        +
        IllegalArgumentException - if count is negative
        +
        IOException - when an I/O error occurs
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/SearchResult.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/SearchResult.html new file mode 100644 index 0000000..04fbc8b --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/SearchResult.html @@ -0,0 +1,288 @@ + + + + + +SearchResult (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface SearchResult

+
+
+
+ +
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getTraces

        +
        Stream<SearchTrace> getTraces()
        +
        Returns all found traces.
        +
        +
        Returns:
        +
        found traces.
        +
        +
      • +
      + + + +
        +
      • +

        getTotalHits

        +
        long getTotalHits()
        +
        Returns the total number of traces matching the query.
        +
        +
        Returns:
        +
        total number of matching traces
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/SearchTrace.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/SearchTrace.html new file mode 100644 index 0000000..d94d8d4 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/SearchTrace.html @@ -0,0 +1,298 @@ + + + + + +SearchTrace (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface SearchTrace

+
+
+
+
    +
  • +
    +
    All Superinterfaces:
    +
    ImmutableTrace
    +
    +
    +
    public interface SearchTrace
    +extends ImmutableTrace
    +
    A trace contains information about processed data. A trace should conform to the trace model defined by Hansken. + Unlike a Trace, the SearchTrace contains information about the searched trace. It is able to + directly retrieve all the data contexts belonging to a searched trace. When data type is known a + RandomAccessData can be retrieved directly.
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getData

        +
        RandomAccessData getData​(String type)
        +
        Returns a RandomAccessData for a specific trace data type.
        +
        +
        Parameters:
        +
        type - the data type of the data stream to be retrieved
        +
        Returns:
        +
        the data stream
        +
        +
      • +
      + + + +
        +
      • +

        getDataTypes

        +
        List<String> getDataTypes()
        +
        Returns all available data types for this search trace.
        +
        +
        Returns:
        +
        available data types
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Trace.Tracelet.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Trace.Tracelet.html new file mode 100644 index 0000000..a2166c5 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Trace.Tracelet.html @@ -0,0 +1,331 @@ + + + + + +Trace.Tracelet (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class Trace.Tracelet

+
+
+ +
+
    +
  • +
    +
    Enclosing interface:
    +
    Trace
    +
    +
    +
    public static class Trace.Tracelet
    +extends Object
    +
    a Tracelet represents tracedata that can be present multiple times within a trace. + The API doesn't specify the cardinality , but the implementation is limited to + cardinality Few.
    +
  • +
+
+
+ +
+
+ +
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Trace.TraceletBuilder.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Trace.TraceletBuilder.html new file mode 100644 index 0000000..1fa838d --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Trace.TraceletBuilder.html @@ -0,0 +1,270 @@ + + + + + +Trace.TraceletBuilder (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface Trace.TraceletBuilder

+
+
+
+
    +
  • +
    +
    Enclosing interface:
    +
    Trace
    +
    +
    +
    public static interface Trace.TraceletBuilder
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        set

        +
        Trace.TraceletBuilder set​(String name,
        +                          Object value)
        +
        Set a property on this tracelet with a given value.
        +
        +
        Parameters:
        +
        name - the name of the property to set
        +
        value - the value to set
        +
        Returns:
        +
        this
        +
        Throws:
        +
        IllegalArgumentException - if the property or value of the property is invalid as per the trace model
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Trace.TraceletProperty.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Trace.TraceletProperty.html new file mode 100644 index 0000000..f89d001 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Trace.TraceletProperty.html @@ -0,0 +1,329 @@ + + + + + +Trace.TraceletProperty (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class Trace.TraceletProperty

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.hansken.plugin.extraction.api.Trace.TraceletProperty
    • +
    +
  • +
+
+
    +
  • +
    +
    Enclosing interface:
    +
    Trace
    +
    +
    +
    public static class Trace.TraceletProperty
    +extends Object
    +
    a TraceletProperty is a property of a Tracelet.
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        TraceletProperty

        +
        public TraceletProperty​(String name,
        +                        Object value)
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getName

        +
        public String getName()
        +
      • +
      + + + +
        +
      • +

        getValue

        +
        public Object getValue()
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Trace.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Trace.html new file mode 100644 index 0000000..158b8a3 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Trace.html @@ -0,0 +1,625 @@ + + + + + +Trace (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface Trace

+
+
+
+
    +
  • +
    +
    All Superinterfaces:
    +
    ImmutableTrace
    +
    +
    +
    public interface Trace
    +extends ImmutableTrace
    +
    A trace contains information about processed data. A trace should conform to the trace model defined by Hansken. +

    + A trace can have multiple types (e.g. file or chat). For these types, the trace can contain a set of properties and + associated values. A trace can be associated with a set of data sequences. + During extraction, an extraction plugin may receive a currently processed trace + and accompanying data sequence. +

    + Implementers of a plugin should ensure that setting a property on a trace is valid as per the trace model + which is defined for the traces generated by the plugin. +

    + Note: implementations are not required to implement any kind of thread safety. + It is up to the client to ensure this if necessary.

    +
    +
    See Also:
    +
    ExtractionPlugin.process(Trace, DataContext)
    +
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        addType

        +
        Trace addType​(String type)
        +
        Add a type to this trace (for example: "file"). If this trace already had given type, + nothing changes.
        +
        +
        Parameters:
        +
        type - the type to add
        +
        Returns:
        +
        this
        +
        +
      • +
      + + + +
        +
      • +

        set

        +
        Trace set​(String name,
        +          Object value)
        +
        Set a property on this trace with a given value.
        +
        +
        Parameters:
        +
        name - the name of the property to set
        +
        value - the value of the property
        +
        Returns:
        +
        this
        +
        Throws:
        +
        IllegalArgumentException - if the property or value of the property is invalid as per the trace model
        +
        +
      • +
      + + + +
        +
      • +

        addTracelet

        +
        default Trace addTracelet​(String type,
        +                          Consumer<Trace.TraceletBuilder> callback)
        +
        Add a tracelet to the trace. +

        + Example usage: +

        
        +     trace.addTracelet("prediction", tracelet -> tracelet
        +         .set("type", "type")
        +         .set("model", "model")
        +         .set("label", "label")
        +         .set("confidence", .50));
        +     );
        + }
        +
        +
        Parameters:
        +
        type - the type of tracelet to add
        +
        callback - callback to set the tracelet properties
        +
        Returns:
        +
        this
        +
        +
      • +
      + + + +
        +
      • +

        addTracelet

        +
        @Deprecated
        +Trace addTracelet​(Trace.Tracelet tracelet)
        +
        Deprecated. +
        use addTracelet(type, callback)
        +
        +
        Add a tracelet to the trace.
        +
        +
        Parameters:
        +
        tracelet - the name and properties of the Tracelet
        +
        Returns:
        +
        this
        +
        +
      • +
      + + + +
        +
      • +

        setData

        +
        Trace setData​(String dataType,
        +              DataWriter writer)
        +       throws IOException
        +
        Add a data stream of a given type to this Trace (for example, 'raw' or 'html'). + A trace can only have a single data stream for each data type. A callback function can be passed + which receives an OutputStream to write the data to. +

        + Note: the received output stream should not be closed by the user. It should + also only be used within the scope of the callback, other usage may be guarded against or otherwise + result in undefined behaviour. +

        + Example usage: +

        
        +     final PacketSequencer sequencer = ...;
        +     final RandomAccessData input = dataContext.data();
        +
        +     trace.setData("packets", data -> {
        +         sequencer.process(input).forEach(packet -> {
        +             data.write(packet);
        +         });
        +     });
        + 
        +
        +
        Parameters:
        +
        dataType - the type of the data stream to add
        +
        writer - callback that receives the stream to write to as input
        +
        Returns:
        +
        this
        +
        Throws:
        +
        IllegalArgumentException - if this trace already had an associated data stream of given type
        +
        IOException - when the given callback throws one
        +
        +
      • +
      + + + +
        +
      • +

        setData

        +
        default Trace setData​(String dataType,
        +                      DataTransformation... transformations)
        +
        Set a series of data transformations for a specific dataType. + + Transformations are pointers to actual raw data. They describe how data can be obtained by reading on certain + positions, using decryption, or combinations of these. The benefit of using Transformations is that they take up + less space than the actual data.
        +
        +
        Parameters:
        +
        dataType - the type of the datastream
        +
        transformations - the data transformations
        +
        Returns:
        +
        this
        +
        +
      • +
      + + + +
        +
      • +

        setData

        +
        Trace setData​(String dataType,
        +              List<DataTransformation> transformation)
        +
        Set a series of data transformations for a specific dataType.
        +
        +
        Parameters:
        +
        dataType - the type of the datastream
        +
        transformation - the data transformations
        +
        Returns:
        +
        this
        +
        +
      • +
      + + + +
        +
      • +

        setData

        +
        default Trace setData​(String dataType,
        +                      InputStream stream)
        +               throws IOException
        +
        Add a data stream of a given type to this Trace (for example, 'raw' or 'html'). + A trace can only have a single data stream for each data type. +

        + The given InputStream will not be closed by this method. +

        + Example usage: +

        
        +     final ThumbnailDetector detector = ...;
        +     final RandomAccessData input = dataContext.data();
        +     final InputStream thumbnails = detector.detect(input);
        +     trace.setData("preview", thumbnails);
        + 
        +
        +
        Parameters:
        +
        dataType - the type of the data stream to add
        +
        stream - the data stream itself
        +
        Returns:
        +
        this
        +
        Throws:
        +
        IllegalArgumentException - if this trace already had an associated data stream of given type
        +
        IOException - when an I/O error occurs while reading the input
        +
        +
      • +
      + + + +
        +
      • +

        newChild

        +
        Trace newChild​(String name,
        +               ThrowingConsumer<Trace,​IOException> enrichChildCallback)
        +        throws IOException
        +
        Create and store new child trace of this trace. A callback function + can be passed in order to enrich the created child trace (which will have no types + or properties set yet), or even recursively add new children under it. After the + trace goes out of scope of the callback, it can no longer be updated. +

        + As an example, say we have a Node type with the following API +

        
        +   interface Node {
        +     String name();
        +     List<Node> children();
        +   }
        +
        + A recursive function for creating traces from a node, where the initial node + should be mapped to a single child under the trace being processed: +
        
        +     public void process(final Trace trace, final DataContext dataContext) {
        +         final Node node = createNode(); // get the node from somewhere
        +         createTree(node, trace);
        +     }
        +
        +     void createTree(final Node childNode, final Trace parentTrace) {
        +         parentTrace.newChild(childNode.name(), child -> {
        +             // set information from child node on child trace
        +             child.type("file").set("file.name", "password.txt");
        +             // recursively create more children
        +             childNode.children().forEach(node -> {
        +                 createTree(node, child);
        +             });
        +         });
        +     }
        + 
        +
        +
        Parameters:
        +
        name - the name of the child
        +
        enrichChildCallback - callback that gets the new child trace as input
        +
        Returns:
        +
        this
        +
        Throws:
        +
        IllegalStateException - if the trace was not yet saved
        +
        IOException - when the given callback throws one
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/TraceSearcher.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/TraceSearcher.html new file mode 100644 index 0000000..67aaf1a --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/TraceSearcher.html @@ -0,0 +1,270 @@ + + + + + +TraceSearcher (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface TraceSearcher

+
+
+
+
    +
  • +
    +
    public interface TraceSearcher
    +
    Allows searching for traces within the scope of the process function.
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        search

        +
        SearchResult search​(String query,
        +                    int count)
        +             throws InterruptedException,
        +                    ExecutionException
        +
        Searches in Hansken for Traces matching provided query.
        +
        +
        Parameters:
        +
        query - Search query to match traces. This is a HQL query.
        +
        count - Maximum number of traces to return.
        +
        Returns:
        +
        SearchResult containing traces matching the provided query.
        +
        Throws:
        +
        InterruptedException - if a thread searching for traces is interrupted.
        +
        ExecutionException - if searching for traces is throws an exception.
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Vector.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Vector.html new file mode 100644 index 0000000..8fdfa66 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/Vector.html @@ -0,0 +1,496 @@ + + + + + +Vector (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class Vector

+
+
+ +
+
    +
  • +
    +
    public final class Vector
    +extends Object
    +
    An opaque vector of floating point values.
    +
    +
    Author:
    +
    Netherlands Forensic Institute
    +
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        of

        +
        public static Vector of​(float... values)
        +
        Creates a Vector from an array of floating point values.
        +
        +
        Parameters:
        +
        values - the values to use
        +
        Returns:
        +
        a vector
        +
        +
      • +
      + + + +
        +
      • +

        of

        +
        public static Vector of​(Collection<Float> values)
        +
        Creates a Vector from a collection of numbers. + Note that all numbers are converted to floats internally, so loss of precision may occur when doubles or longs are offered.
        +
        +
        Parameters:
        +
        values - the values to use
        +
        Returns:
        +
        a vector
        +
        +
      • +
      + + + +
        +
      • +

        ofBase64

        +
        public static Vector ofBase64​(String base64)
        +
        Creates a Vector from a base64 encoded string.
        +
        +
        Parameters:
        +
        base64 - the base64 encoded string to use
        +
        Returns:
        +
        a vector
        +
        +
      • +
      + + + +
        +
      • +

        asVector

        +
        public static Vector asVector​(byte[] bytes)
        +
        Creates a vector from a binary representation. + Vector.asBinary() can be used to obtain a binary representation. + Note that this directly sets the internal state of the Vector, use {code asVector(bytes.clone()} to store a safe, immutable copy.
        +
        +
        Parameters:
        +
        bytes - the bytes to convert to a vector.
        +
        Returns:
        +
        a vector
        +
        +
      • +
      + + + +
        +
      • +

        asBinary

        +
        public byte[] asBinary()
        +
        Returns the binary representation of the Vector. + Vector.asVector(byte[]) can be used to convert the binary representation back to the original vector. + The format of the returned bytes is a sequence of the floating point values of the vector, stored as big-endian IEEE 754 encoded 32-bit floating point values. + Note that this exposes the internal state of the Vector, use {code asBinary().clone()} to obtain a safely mutable copy.
        +
        +
        Returns:
        +
        a binary representation of the vector.
        +
        +
      • +
      + + + +
        +
      • +

        size

        +
        public int size()
        +
        Returns the number of dimensions of the vector.
        +
        +
        Returns:
        +
        the number of dimensions of the vector.
        +
        +
      • +
      + + + +
        +
      • +

        values

        +
        public float[] values()
        +
        Returns the values of the Vector as an array of floats.
        +
        +
        Returns:
        +
        the values of the Vector as an array of floats.
        +
        +
      • +
      + + + +
        +
      • +

        toBase64

        +
        public String toBase64()
        +
        Returns a base64 encoded string of the Vector. + Vector.ofBase64() can be used to convert the return base64 encoded string back to a Vector.
        +
        +
        Returns:
        +
        a base64 encoded string of the Vector.
        +
        +
      • +
      + + + + + + + +
        +
      • +

        hashCode

        +
        public int hashCode()
        +
        +
        Overrides:
        +
        hashCode in class Object
        +
        +
      • +
      + + + +
        +
      • +

        equals

        +
        public boolean equals​(Object obj)
        +
        +
        Overrides:
        +
        equals in class Object
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Author.Builder.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Author.Builder.html new file mode 100644 index 0000000..ee801da --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Author.Builder.html @@ -0,0 +1,219 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.Author.Builder (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.Author.Builder

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Author.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Author.html new file mode 100644 index 0000000..bd369e6 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Author.html @@ -0,0 +1,222 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.Author (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.Author

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/BaseExtractionPlugin.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/BaseExtractionPlugin.html new file mode 100644 index 0000000..1f2f905 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/BaseExtractionPlugin.html @@ -0,0 +1,239 @@ + + + + + +Uses of Interface org.hansken.plugin.extraction.api.BaseExtractionPlugin (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
org.hansken.plugin.extraction.api.BaseExtractionPlugin

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/BatchSearchResult.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/BatchSearchResult.html new file mode 100644 index 0000000..6b40b76 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/BatchSearchResult.html @@ -0,0 +1,150 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.BatchSearchResult (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.BatchSearchResult

+
+
No usage of org.hansken.plugin.extraction.api.BatchSearchResult
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/DataContext.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/DataContext.html new file mode 100644 index 0000000..133c282 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/DataContext.html @@ -0,0 +1,214 @@ + + + + + +Uses of Interface org.hansken.plugin.extraction.api.DataContext (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
org.hansken.plugin.extraction.api.DataContext

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/DataWriter.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/DataWriter.html new file mode 100644 index 0000000..d936927 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/DataWriter.html @@ -0,0 +1,199 @@ + + + + + +Uses of Interface org.hansken.plugin.extraction.api.DataWriter (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
org.hansken.plugin.extraction.api.DataWriter

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/DeferredExtractionPlugin.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/DeferredExtractionPlugin.html new file mode 100644 index 0000000..89133c2 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/DeferredExtractionPlugin.html @@ -0,0 +1,150 @@ + + + + + +Uses of Interface org.hansken.plugin.extraction.api.DeferredExtractionPlugin (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
org.hansken.plugin.extraction.api.DeferredExtractionPlugin

+
+
No usage of org.hansken.plugin.extraction.api.DeferredExtractionPlugin
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/ExtractionPlugin.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/ExtractionPlugin.html new file mode 100644 index 0000000..221d423 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/ExtractionPlugin.html @@ -0,0 +1,198 @@ + + + + + +Uses of Interface org.hansken.plugin.extraction.api.ExtractionPlugin (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
org.hansken.plugin.extraction.api.ExtractionPlugin

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/ImmutableTrace.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/ImmutableTrace.html new file mode 100644 index 0000000..96a6978 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/ImmutableTrace.html @@ -0,0 +1,205 @@ + + + + + +Uses of Interface org.hansken.plugin.extraction.api.ImmutableTrace (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
org.hansken.plugin.extraction.api.ImmutableTrace

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/LatLong.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/LatLong.html new file mode 100644 index 0000000..ec768e7 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/LatLong.html @@ -0,0 +1,200 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.LatLong (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.LatLong

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/MaturityLevel.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/MaturityLevel.html new file mode 100644 index 0000000..fe5c831 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/MaturityLevel.html @@ -0,0 +1,230 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.MaturityLevel (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.MaturityLevel

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/MetaExtractionPlugin.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/MetaExtractionPlugin.html new file mode 100644 index 0000000..e827339 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/MetaExtractionPlugin.html @@ -0,0 +1,150 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.MetaExtractionPlugin (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.MetaExtractionPlugin

+
+
No usage of org.hansken.plugin.extraction.api.MetaExtractionPlugin
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginId.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginId.html new file mode 100644 index 0000000..b753d7c --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginId.html @@ -0,0 +1,215 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.PluginId (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.PluginId

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginInfo.Builder.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginInfo.Builder.html new file mode 100644 index 0000000..c3f0a48 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginInfo.Builder.html @@ -0,0 +1,293 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.PluginInfo.Builder (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.PluginInfo.Builder

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginInfo.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginInfo.html new file mode 100644 index 0000000..fa2f58a --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginInfo.html @@ -0,0 +1,206 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.PluginInfo (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.PluginInfo

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginResources.Builder.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginResources.Builder.html new file mode 100644 index 0000000..aa609f4 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginResources.Builder.html @@ -0,0 +1,206 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.PluginResources.Builder (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.PluginResources.Builder

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginResources.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginResources.html new file mode 100644 index 0000000..3fc354c --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginResources.html @@ -0,0 +1,220 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.PluginResources (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.PluginResources

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginType.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginType.html new file mode 100644 index 0000000..74bd810 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/PluginType.html @@ -0,0 +1,230 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.PluginType (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.PluginType

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/RandomAccessData.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/RandomAccessData.html new file mode 100644 index 0000000..079251c --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/RandomAccessData.html @@ -0,0 +1,205 @@ + + + + + +Uses of Interface org.hansken.plugin.extraction.api.RandomAccessData (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
org.hansken.plugin.extraction.api.RandomAccessData

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/SearchResult.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/SearchResult.html new file mode 100644 index 0000000..34ab317 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/SearchResult.html @@ -0,0 +1,216 @@ + + + + + +Uses of Interface org.hansken.plugin.extraction.api.SearchResult (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
org.hansken.plugin.extraction.api.SearchResult

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/SearchTrace.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/SearchTrace.html new file mode 100644 index 0000000..3de23ee --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/SearchTrace.html @@ -0,0 +1,220 @@ + + + + + +Uses of Interface org.hansken.plugin.extraction.api.SearchTrace (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
org.hansken.plugin.extraction.api.SearchTrace

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Trace.Tracelet.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Trace.Tracelet.html new file mode 100644 index 0000000..1ad1cc0 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Trace.Tracelet.html @@ -0,0 +1,200 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.Trace.Tracelet (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.Trace.Tracelet

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Trace.TraceletBuilder.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Trace.TraceletBuilder.html new file mode 100644 index 0000000..927d6e0 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Trace.TraceletBuilder.html @@ -0,0 +1,217 @@ + + + + + +Uses of Interface org.hansken.plugin.extraction.api.Trace.TraceletBuilder (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
org.hansken.plugin.extraction.api.Trace.TraceletBuilder

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Trace.TraceletProperty.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Trace.TraceletProperty.html new file mode 100644 index 0000000..7f2e084 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Trace.TraceletProperty.html @@ -0,0 +1,210 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.Trace.TraceletProperty (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.Trace.TraceletProperty

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Trace.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Trace.html new file mode 100644 index 0000000..d0fd453 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Trace.html @@ -0,0 +1,321 @@ + + + + + +Uses of Interface org.hansken.plugin.extraction.api.Trace (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
org.hansken.plugin.extraction.api.Trace

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/TraceSearcher.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/TraceSearcher.html new file mode 100644 index 0000000..0bcaaae --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/TraceSearcher.html @@ -0,0 +1,200 @@ + + + + + +Uses of Interface org.hansken.plugin.extraction.api.TraceSearcher (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
org.hansken.plugin.extraction.api.TraceSearcher

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Vector.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Vector.html new file mode 100644 index 0000000..0791fea --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/class-use/Vector.html @@ -0,0 +1,219 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.Vector (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.Vector

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/package-summary.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/package-summary.html new file mode 100644 index 0000000..7a0378f --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/package-summary.html @@ -0,0 +1,363 @@ + + + + + +org.hansken.plugin.extraction.api (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Package org.hansken.plugin.extraction.api

+
+
+
+ + +
This is the API of the Extraction Plugins SDK. +

+ + This module defines the interfaces used for the Extraction Plugin SDK. +

+
+
Since:
+
0.0.1
+
Author:
+
Netherlands Forensic Institute
+
+
+ +
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/package-tree.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/package-tree.html new file mode 100644 index 0000000..8b7c166 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/package-tree.html @@ -0,0 +1,223 @@ + + + + + +org.hansken.plugin.extraction.api Class Hierarchy (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package org.hansken.plugin.extraction.api

+Package Hierarchies: + +
+
+
+

Class Hierarchy

+ +
+
+

Interface Hierarchy

+ +
+
+

Enum Hierarchy

+ +
+
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/package-use.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/package-use.html new file mode 100644 index 0000000..2263c9e --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/package-use.html @@ -0,0 +1,327 @@ + + + + + +Uses of Package org.hansken.plugin.extraction.api (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Package
org.hansken.plugin.extraction.api

+
+
+ +
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/DataRange.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/DataRange.html new file mode 100644 index 0000000..c6d889e --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/DataRange.html @@ -0,0 +1,365 @@ + + + + + +DataRange (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+ +
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.hansken.plugin.extraction.api.transformations.DataRange
    • +
    +
  • +
+
+
    +
  • +
    +
    public class DataRange
    +extends Object
    +
    A DataRange describes a range of bytes with an offset and length.
    +
    +
    Author:
    +
    Netherlands Forensic Institute
    +
    +
  • +
+
+
+ +
+
+
    +
  • + +
    +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        DataRange

        +
        public DataRange​(long offset,
        +                 long length)
        +
        Creates a DataRange which describes a range of bytes.
        +
        +
        Parameters:
        +
        offset - the starting point of the data
        +
        length - the size of the data
        +
        +
      • +
      +
    • +
    +
    + +
    +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getOffset

        +
        public long getOffset()
        +
      • +
      + + + +
        +
      • +

        getLength

        +
        public long getLength()
        +
      • +
      + + + +
        +
      • +

        setOffset

        +
        public void setOffset​(long offset)
        +
      • +
      + + + +
        +
      • +

        setLength

        +
        public void setLength​(long length)
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/DataTransformation.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/DataTransformation.html new file mode 100644 index 0000000..cfb03f3 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/DataTransformation.html @@ -0,0 +1,204 @@ + + + + + +DataTransformation (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface DataTransformation

+
+
+
+ +
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/RangedDataTransformation.Builder.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/RangedDataTransformation.Builder.html new file mode 100644 index 0000000..cec3089 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/RangedDataTransformation.Builder.html @@ -0,0 +1,358 @@ + + + + + +RangedDataTransformation.Builder (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class RangedDataTransformation.Builder

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.hansken.plugin.extraction.api.transformations.RangedDataTransformation.Builder
    • +
    +
  • +
+
+ +
+
+ +
+
+ +
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/RangedDataTransformation.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/RangedDataTransformation.html new file mode 100644 index 0000000..fc960e8 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/RangedDataTransformation.html @@ -0,0 +1,406 @@ + + + + + +RangedDataTransformation (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class RangedDataTransformation

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.hansken.plugin.extraction.api.transformations.RangedDataTransformation
    • +
    +
  • +
+
+ +
+
+ +
+
+ +
+
+
+ + + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/class-use/DataRange.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/class-use/DataRange.html new file mode 100644 index 0000000..c192133 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/class-use/DataRange.html @@ -0,0 +1,261 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.transformations.DataRange (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.transformations.DataRange

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/class-use/DataTransformation.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/class-use/DataTransformation.html new file mode 100644 index 0000000..98a3928 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/class-use/DataTransformation.html @@ -0,0 +1,247 @@ + + + + + +Uses of Interface org.hansken.plugin.extraction.api.transformations.DataTransformation (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
org.hansken.plugin.extraction.api.transformations.DataTransformation

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/class-use/RangedDataTransformation.Builder.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/class-use/RangedDataTransformation.Builder.html new file mode 100644 index 0000000..bf06d9a --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/class-use/RangedDataTransformation.Builder.html @@ -0,0 +1,214 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.transformations.RangedDataTransformation.Builder (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.transformations.RangedDataTransformation.Builder

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/class-use/RangedDataTransformation.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/class-use/RangedDataTransformation.html new file mode 100644 index 0000000..cb34a41 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/class-use/RangedDataTransformation.html @@ -0,0 +1,196 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.api.transformations.RangedDataTransformation (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.api.transformations.RangedDataTransformation

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/package-summary.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/package-summary.html new file mode 100644 index 0000000..41e03a6 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/package-summary.html @@ -0,0 +1,216 @@ + + + + + +org.hansken.plugin.extraction.api.transformations (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Package org.hansken.plugin.extraction.api.transformations

+
+
+
+ + +
This package contains the Data Transformations. +

+ + Currently only the RangedDataTransformation is supported. +

+
+
Since:
+
0.3.0
+
Author:
+
Netherlands Forensic Institute
+
+
+ +
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/package-tree.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/package-tree.html new file mode 100644 index 0000000..eb90f0d --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/package-tree.html @@ -0,0 +1,173 @@ + + + + + +org.hansken.plugin.extraction.api.transformations Class Hierarchy (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package org.hansken.plugin.extraction.api.transformations

+Package Hierarchies: + +
+
+
+

Class Hierarchy

+ +
+
+

Interface Hierarchy

+ +
+
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/package-use.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/package-use.html new file mode 100644 index 0000000..22c5f7b --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/api/transformations/package-use.html @@ -0,0 +1,235 @@ + + + + + +Uses of Package org.hansken.plugin.extraction.api.transformations (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Package
org.hansken.plugin.extraction.api.transformations

+
+
+ +
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/ArgChecks.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/ArgChecks.html new file mode 100644 index 0000000..b52c99f --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/ArgChecks.html @@ -0,0 +1,627 @@ + + + + + +ArgChecks (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Class ArgChecks

+
+
+ +
+
    +
  • +
    +
    public final class ArgChecks
    +extends Object
    +
    A collection of methods for defensively checking arguments passed to methods.
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethodDescription
      static Object[]argNotAllNull​(String name, + Object... values) +
      Check that not all values with given name are null, otherwise throw an exception.
      +
      static StringargNotEmpty​(String name, + String value) +
      Check that the string with given name is not null or empty, otherwise throw an exception.
      +
      static <T> Collection<T>argNotEmpty​(String name, + Collection<T> value) +
      Check that the collection value with given name is not empty, otherwise throw an exception.
      +
      static <T> T[]argNotEmpty​(String name, + T[] value) +
      Check that the array value with given name is not empty, otherwise throw an exception.
      +
      static floatargNotNegative​(String name, + float value) +
      Check that the float with given name is not negative, otherwise throw an exception.
      +
      static intargNotNegative​(String name, + int value) +
      Check that the int with given name is not negative, otherwise throw an exception.
      +
      static longargNotNegative​(String name, + long value) +
      Check that the long with given name is not negative, otherwise throw an exception.
      +
      static <T> TargNotNull​(String name, + T value) +
      Check that the value with given name is not null, otherwise throw an exception.
      +
      static <T> List<T>argsIsType​(String name, + List<T> value, + Class<?> type) +
      Check that the value with given name is of type type, otherwise throw an exception.
      +
      + +
    • +
    +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + +
        +
      • +

        argNotNull

        +
        public static <T> T argNotNull​(String name,
        +                               T value)
        +
        Check that the value with given name is not null, otherwise throw an exception. +

        + Example usage: +

        + 
        +     private final Author author;
        +
        +     public Book(final Author author) {
        +          // this throws a NullPointerException when the author is null
        +          this.author = argNotNull("author", author);
        +     }
        + 
        + 
        +
        +
        Type Parameters:
        +
        T - the type of the value
        +
        Parameters:
        +
        name - a descriptive name for the value (most likely the argument name)
        +
        value - the value itself
        +
        Returns:
        +
        the value, so it can be used to inline in an assignment
        +
        Throws:
        +
        NullPointerException - if value == null
        +
        +
      • +
      + + + +
        +
      • +

        argsIsType

        +
        public static <T> List<T> argsIsType​(String name,
        +                                     List<T> value,
        +                                     Class<?> type)
        +
        Check that the value with given name is of type type, otherwise throw an exception. +

        + Example usage: +

        + 
        +     public Book(final List<Object> authors) {
        +          // this throws a ClassCastException when authors contains a type other than Author.class
        +          argsIsType("author", authors, Author.class);
        +     }
        + 
        + 
        +
        +
        Type Parameters:
        +
        T - the type of the value
        +
        Parameters:
        +
        name - a descriptive name for the value (most likely the argument name)
        +
        value - the value itself
        +
        type - the class type we want to check against value
        +
        Returns:
        +
        the value, so it can be used to inline in an assignment
        +
        Throws:
        +
        ClassCastException - if value.allMatch(type::isInstance) is false
        +
        +
      • +
      + + + +
        +
      • +

        argNotAllNull

        +
        public static Object[] argNotAllNull​(String name,
        +                                     Object... values)
        +
        Check that not all values with given name are null, otherwise throw an exception. +

        + Example usage: +

        + 
        +     private final Author author;
        +
        +     public Book(final Author... authors) {
        +          // this throws a NullPointerException when the author is null
        +          this.author = argNotAllNull("authors", authors);
        +     }
        + 
        + 
        +
        +
        Parameters:
        +
        name - a descriptive name for the values (most likely the argument name)
        +
        values - the values, which can be different types
        +
        Returns:
        +
        the values, so it can be used to inline in an assignment
        +
        Throws:
        +
        NullPointerException - if values.allMatch(Objects::isNull)
        +
        +
      • +
      + + + + + +
        +
      • +

        argNotEmpty

        +
        public static <T> T[] argNotEmpty​(String name,
        +                                  T[] value)
        +
        Check that the array value with given name is not empty, otherwise throw an exception. +

        + Example usage: +

        + 
        +     private final Author[] author;
        +
        +     public Book(final Author[] author) {
        +          // this throws a NullPointerException when the author array is empty
        +          this.author = argNotEmpty("author", author);
        +     }
        + 
        + 
        +
        +
        Type Parameters:
        +
        T - the type of the value
        +
        Parameters:
        +
        name - a descriptive name for the value (most likely the argument name)
        +
        value - the value itself
        +
        Returns:
        +
        the value, so it can be used to inline in an assignment
        +
        Throws:
        +
        IllegalArgumentException - if value == null || value.length == 0
        +
        +
      • +
      + + + +
        +
      • +

        argNotEmpty

        +
        public static <T> Collection<T> argNotEmpty​(String name,
        +                                            Collection<T> value)
        +
        Check that the collection value with given name is not empty, otherwise throw an exception. +

        + Example usage: +

        + 
        +     private final Collection<Author> author;
        +
        +     public Book(final Collection<Author> author) {
        +          // this throws a NullPointerException when the author collection is empty
        +          this.author = argNotEmpty("author", author);
        +     }
        + 
        + 
        +
        +
        Type Parameters:
        +
        T - the type of the value
        +
        Parameters:
        +
        name - a descriptive name for the value (most likely the argument name)
        +
        value - the value itself
        +
        Returns:
        +
        the value, so it can be used to inline in an assignment
        +
        Throws:
        +
        IllegalArgumentException - if value == null || value.isEmpty()
        +
        +
      • +
      + + + +
        +
      • +

        argNotEmpty

        +
        public static String argNotEmpty​(String name,
        +                                 String value)
        +
        Check that the string with given name is not null or empty, otherwise throw an exception. +

        + Example usage: +

        + 
        +     private final String title;
        +
        +     public Book(final String title) {
        +          // this throws a NullPointerException when the author is null
        +          // or an IllegalArgumentException when author is an empty String
        +          this.title = argNotEmpty("title", title);
        +     }
        + 
        + 
        +
        +
        Parameters:
        +
        name - a descriptive name for the string (most likely the argument name)
        +
        value - the string itself
        +
        Returns:
        +
        the string, so it can be used to inline in an assignment
        +
        Throws:
        +
        NullPointerException - if value == null
        +
        IllegalArgumentException - if value.isEmpty()
        +
        +
      • +
      + + + +
        +
      • +

        argNotNegative

        +
        public static int argNotNegative​(String name,
        +                                 int value)
        +
        Check that the int with given name is not negative, otherwise throw an exception. + Example usage: +
        + 
        +     private final int pageCount;
        +
        +     public Book(final int pageCount) {
        +          // this throws an IllegalArgumentException when pageCount is negative
        +          this.pageCount = argNotNegative("pageCount", pageCount);
        +     }
        + 
        + 
        +
        +
        Parameters:
        +
        name - a descriptive name for the int (most likely the argument name)
        +
        value - the int itself
        +
        Returns:
        +
        the int, so it can be used in an inline assignment
        +
        Throws:
        +
        IllegalArgumentException - if value < 0
        +
        +
      • +
      + + + +
        +
      • +

        argNotNegative

        +
        public static long argNotNegative​(String name,
        +                                  long value)
        +
        Check that the long with given name is not negative, otherwise throw an exception. + Example usage: +
        + 
        +     private final long pageCount;
        +
        +     public Book(final long pageCount) {
        +          // this throws an IllegalArgumentException when pageCount is negative
        +          this.pageCount = argNotNegative("pageCount", pageCount);
        +     }
        + 
        + 
        +
        +
        Parameters:
        +
        name - a descriptive name for the long (most likely the argument name)
        +
        value - the long itself
        +
        Returns:
        +
        the long, so it can be used in an inline assignment
        +
        Throws:
        +
        IllegalArgumentException - if value < 0
        +
        +
      • +
      + + + +
        +
      • +

        argNotNegative

        +
        public static float argNotNegative​(String name,
        +                                   float value)
        +
        Check that the float with given name is not negative, otherwise throw an exception. + Example usage: +
        + 
        +     private final float pageCount;
        +
        +     public Book(final float pageCount) {
        +          // this throws an IllegalArgumentException when pageCount is negative
        +          this.pageCount = argNotNegative("pageCount", pageCount);
        +     }
        + 
        + 
        +
        +
        Parameters:
        +
        name - a descriptive name for the float (most likely the argument name)
        +
        value - the float itself
        +
        Returns:
        +
        the float, so it can be used in an inline assignment
        +
        Throws:
        +
        IllegalArgumentException - if value < 0
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/ThrowingConsumer.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/ThrowingConsumer.html new file mode 100644 index 0000000..91e0191 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/ThrowingConsumer.html @@ -0,0 +1,278 @@ + + + + + +ThrowingConsumer (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +

Interface ThrowingConsumer<T,​E extends Throwable>

+
+
+
+
    +
  • +
    +
    Type Parameters:
    +
    T - the type of the input to the operation
    +
    E - the type of the Throwable which can be thrown by the operation
    +
    +
    +
    Functional Interface:
    +
    This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.
    +
    +
    +
    @FunctionalInterface
    +public interface ThrowingConsumer<T,​E extends Throwable>
    +
    Represents an operation that accepts a single input argument and returns no result. + Replaces the normal Java Consumer and enables throwing an Exception from the callback. Used to enable + lambda callbacks in for example Trace.newChild(String, ThrowingConsumer).
    +
  • +
+
+
+
    +
  • + +
    + +
    +
  • +
+
+
+
    +
  • + +
    +
      +
    • + + +

      Method Detail

      + + + + + +
        +
      • +

        accept

        +
        void accept​(T t)
        +     throws E extends Throwable
        +
        Performs this operation on the given argument.
        +
        +
        Parameters:
        +
        t - the input argument
        +
        Throws:
        +
        E - when an exception occurs
        +
        E extends Throwable
        +
        +
      • +
      +
    • +
    +
    +
  • +
+
+
+
+ +
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/class-use/ArgChecks.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/class-use/ArgChecks.html new file mode 100644 index 0000000..43c9a2e --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/class-use/ArgChecks.html @@ -0,0 +1,150 @@ + + + + + +Uses of Class org.hansken.plugin.extraction.util.ArgChecks (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Class
org.hansken.plugin.extraction.util.ArgChecks

+
+
No usage of org.hansken.plugin.extraction.util.ArgChecks
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/class-use/ThrowingConsumer.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/class-use/ThrowingConsumer.html new file mode 100644 index 0000000..ba1991f --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/class-use/ThrowingConsumer.html @@ -0,0 +1,199 @@ + + + + + +Uses of Interface org.hansken.plugin.extraction.util.ThrowingConsumer (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Interface
org.hansken.plugin.extraction.util.ThrowingConsumer

+
+
+ +
+
+ + + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/package-summary.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/package-summary.html new file mode 100644 index 0000000..d5d8590 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/package-summary.html @@ -0,0 +1,199 @@ + + + + + +org.hansken.plugin.extraction.util (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Package org.hansken.plugin.extraction.util

+
+
+
+ + +
This package provides util classes for the Extraction Plugin SDK API. +

+
+
Since:
+
0.0.1
+
Author:
+
Netherlands Forensic Institute
+
+
+
    +
  • + + + + + + + + + + + + +
    Interface Summary 
    InterfaceDescription
    ThrowingConsumer<T,​E extends Throwable> +
    Represents an operation that accepts a single input argument and returns no result.
    +
    +
  • +
  • + + + + + + + + + + + + +
    Class Summary 
    ClassDescription
    ArgChecks +
    A collection of methods for defensively checking arguments passed to methods.
    +
    +
  • +
+
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/package-tree.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/package-tree.html new file mode 100644 index 0000000..98fb716 --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/package-tree.html @@ -0,0 +1,171 @@ + + + + + +org.hansken.plugin.extraction.util Class Hierarchy (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For Package org.hansken.plugin.extraction.util

+Package Hierarchies: + +
+
+
+

Class Hierarchy

+ +
+
+

Interface Hierarchy

+ +
+
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/package-use.html b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/package-use.html new file mode 100644 index 0000000..444001d --- /dev/null +++ b/0.7.0/_static/javadoc/org/hansken/plugin/extraction/util/package-use.html @@ -0,0 +1,189 @@ + + + + + +Uses of Package org.hansken.plugin.extraction.util (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+
+

Uses of Package
org.hansken.plugin.extraction.util

+
+
+ +
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/overview-summary.html b/0.7.0/_static/javadoc/overview-summary.html new file mode 100644 index 0000000..ffd3897 --- /dev/null +++ b/0.7.0/_static/javadoc/overview-summary.html @@ -0,0 +1,23 @@ + + + + + +api 0.7.0 API + + + + + + + +
+ +

index.html

+
+ + diff --git a/0.7.0/_static/javadoc/overview-tree.html b/0.7.0/_static/javadoc/overview-tree.html new file mode 100644 index 0000000..0990bba --- /dev/null +++ b/0.7.0/_static/javadoc/overview-tree.html @@ -0,0 +1,231 @@ + + + + + +Class Hierarchy (api 0.7.0 API) + + + + + + + + + + + + + + + +
+ +
+
+ +
+
+

Class Hierarchy

+ +
+
+

Interface Hierarchy

+ +
+
+

Enum Hierarchy

+ +
+
+
+
+ +

Copyright © 2023. All rights reserved.

+
+ + diff --git a/0.7.0/_static/javadoc/package-search-index.js b/0.7.0/_static/javadoc/package-search-index.js new file mode 100644 index 0000000..53b7dd2 --- /dev/null +++ b/0.7.0/_static/javadoc/package-search-index.js @@ -0,0 +1 @@ +packageSearchIndex = [{"l":"All Packages","url":"allpackages-index.html"},{"l":"org.hansken.plugin.extraction.api"},{"l":"org.hansken.plugin.extraction.api.transformations"},{"l":"org.hansken.plugin.extraction.util"}] \ No newline at end of file diff --git a/0.7.0/_static/javadoc/package-search-index.zip b/0.7.0/_static/javadoc/package-search-index.zip new file mode 100644 index 0000000..8c97814 Binary files /dev/null and b/0.7.0/_static/javadoc/package-search-index.zip differ diff --git a/0.7.0/_static/javadoc/resources/glass.png b/0.7.0/_static/javadoc/resources/glass.png new file mode 100644 index 0000000..a7f591f Binary files /dev/null and b/0.7.0/_static/javadoc/resources/glass.png differ diff --git a/0.7.0/_static/javadoc/resources/x.png b/0.7.0/_static/javadoc/resources/x.png new file mode 100644 index 0000000..30548a7 Binary files /dev/null and b/0.7.0/_static/javadoc/resources/x.png differ diff --git a/0.7.0/_static/javadoc/script.js b/0.7.0/_static/javadoc/script.js new file mode 100644 index 0000000..7dc93c4 --- /dev/null +++ b/0.7.0/_static/javadoc/script.js @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +var moduleSearchIndex; +var packageSearchIndex; +var typeSearchIndex; +var memberSearchIndex; +var tagSearchIndex; +function loadScripts(doc, tag) { + createElem(doc, tag, 'jquery/jszip/dist/jszip.js'); + createElem(doc, tag, 'jquery/jszip-utils/dist/jszip-utils.js'); + if (window.navigator.userAgent.indexOf('MSIE ') > 0 || window.navigator.userAgent.indexOf('Trident/') > 0 || + window.navigator.userAgent.indexOf('Edge/') > 0) { + createElem(doc, tag, 'jquery/jszip-utils/dist/jszip-utils-ie.js'); + } + createElem(doc, tag, 'search.js'); + + $.get(pathtoroot + "module-search-index.zip") + .done(function() { + JSZipUtils.getBinaryContent(pathtoroot + "module-search-index.zip", function(e, data) { + JSZip.loadAsync(data).then(function(zip){ + zip.file("module-search-index.json").async("text").then(function(content){ + moduleSearchIndex = JSON.parse(content); + }); + }); + }); + }); + $.get(pathtoroot + "package-search-index.zip") + .done(function() { + JSZipUtils.getBinaryContent(pathtoroot + "package-search-index.zip", function(e, data) { + JSZip.loadAsync(data).then(function(zip){ + zip.file("package-search-index.json").async("text").then(function(content){ + packageSearchIndex = JSON.parse(content); + }); + }); + }); + }); + $.get(pathtoroot + "type-search-index.zip") + .done(function() { + JSZipUtils.getBinaryContent(pathtoroot + "type-search-index.zip", function(e, data) { + JSZip.loadAsync(data).then(function(zip){ + zip.file("type-search-index.json").async("text").then(function(content){ + typeSearchIndex = JSON.parse(content); + }); + }); + }); + }); + $.get(pathtoroot + "member-search-index.zip") + .done(function() { + JSZipUtils.getBinaryContent(pathtoroot + "member-search-index.zip", function(e, data) { + JSZip.loadAsync(data).then(function(zip){ + zip.file("member-search-index.json").async("text").then(function(content){ + memberSearchIndex = JSON.parse(content); + }); + }); + }); + }); + $.get(pathtoroot + "tag-search-index.zip") + .done(function() { + JSZipUtils.getBinaryContent(pathtoroot + "tag-search-index.zip", function(e, data) { + JSZip.loadAsync(data).then(function(zip){ + zip.file("tag-search-index.json").async("text").then(function(content){ + tagSearchIndex = JSON.parse(content); + }); + }); + }); + }); + if (!moduleSearchIndex) { + createElem(doc, tag, 'module-search-index.js'); + } + if (!packageSearchIndex) { + createElem(doc, tag, 'package-search-index.js'); + } + if (!typeSearchIndex) { + createElem(doc, tag, 'type-search-index.js'); + } + if (!memberSearchIndex) { + createElem(doc, tag, 'member-search-index.js'); + } + if (!tagSearchIndex) { + createElem(doc, tag, 'tag-search-index.js'); + } + $(window).resize(function() { + $('.navPadding').css('padding-top', $('.fixedNav').css("height")); + }); +} + +function createElem(doc, tag, path) { + var script = doc.createElement(tag); + var scriptElement = doc.getElementsByTagName(tag)[0]; + script.src = pathtoroot + path; + scriptElement.parentNode.insertBefore(script, scriptElement); +} + +function show(type) { + count = 0; + for (var key in data) { + var row = document.getElementById(key); + if ((data[key] & type) !== 0) { + row.style.display = ''; + row.className = (count++ % 2) ? rowColor : altColor; + } + else + row.style.display = 'none'; + } + updateTabs(type); +} + +function updateTabs(type) { + for (var value in tabs) { + var sNode = document.getElementById(tabs[value][0]); + var spanNode = sNode.firstChild; + if (value == type) { + sNode.className = activeTableTab; + spanNode.innerHTML = tabs[value][1]; + } + else { + sNode.className = tableTab; + spanNode.innerHTML = "" + tabs[value][1] + ""; + } + } +} + +function updateModuleFrame(pFrame, cFrame) { + top.packageFrame.location = pFrame; + top.classFrame.location = cFrame; +} diff --git a/0.7.0/_static/javadoc/search.js b/0.7.0/_static/javadoc/search.js new file mode 100644 index 0000000..8492271 --- /dev/null +++ b/0.7.0/_static/javadoc/search.js @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +var noResult = {l: "No results found"}; +var catModules = "Modules"; +var catPackages = "Packages"; +var catTypes = "Types"; +var catMembers = "Members"; +var catSearchTags = "SearchTags"; +var highlight = "$&"; +var camelCaseRegexp = ""; +var secondaryMatcher = ""; +function getHighlightedText(item) { + var ccMatcher = new RegExp(camelCaseRegexp); + var label = item.replace(ccMatcher, highlight); + if (label === item) { + label = item.replace(secondaryMatcher, highlight); + } + return label; +} +function getURLPrefix(ui) { + var urlPrefix=""; + if (useModuleDirectories) { + var slash = "/"; + if (ui.item.category === catModules) { + return ui.item.l + slash; + } else if (ui.item.category === catPackages && ui.item.m) { + return ui.item.m + slash; + } else if ((ui.item.category === catTypes && ui.item.p) || ui.item.category === catMembers) { + $.each(packageSearchIndex, function(index, item) { + if (item.m && ui.item.p == item.l) { + urlPrefix = item.m + slash; + } + }); + return urlPrefix; + } else { + return urlPrefix; + } + } + return urlPrefix; +} +var watermark = 'Search'; +$(function() { + $("#search").val(''); + $("#search").prop("disabled", false); + $("#reset").prop("disabled", false); + $("#search").val(watermark).addClass('watermark'); + $("#search").blur(function() { + if ($(this).val().length == 0) { + $(this).val(watermark).addClass('watermark'); + } + }); + $("#search").on('click keydown', function() { + if ($(this).val() == watermark) { + $(this).val('').removeClass('watermark'); + } + }); + $("#reset").click(function() { + $("#search").val(''); + $("#search").focus(); + }); + $("#search").focus(); + $("#search")[0].setSelectionRange(0, 0); +}); +$.widget("custom.catcomplete", $.ui.autocomplete, { + _create: function() { + this._super(); + this.widget().menu("option", "items", "> :not(.ui-autocomplete-category)"); + }, + _renderMenu: function(ul, items) { + var rMenu = this, + currentCategory = ""; + rMenu.menu.bindings = $(); + $.each(items, function(index, item) { + var li; + if (item.l !== noResult.l && item.category !== currentCategory) { + ul.append("
  • " + item.category + "
  • "); + currentCategory = item.category; + } + li = rMenu._renderItemData(ul, item); + if (item.category) { + li.attr("aria-label", item.category + " : " + item.l); + li.attr("class", "resultItem"); + } else { + li.attr("aria-label", item.l); + li.attr("class", "resultItem"); + } + }); + }, + _renderItem: function(ul, item) { + var label = ""; + if (item.category === catModules) { + label = getHighlightedText(item.l); + } else if (item.category === catPackages) { + label = (item.m) + ? getHighlightedText(item.m + "/" + item.l) + : getHighlightedText(item.l); + } else if (item.category === catTypes) { + label = (item.p) + ? getHighlightedText(item.p + "." + item.l) + : getHighlightedText(item.l); + } else if (item.category === catMembers) { + label = getHighlightedText(item.p + "." + (item.c + "." + item.l)); + } else if (item.category === catSearchTags) { + label = getHighlightedText(item.l); + } else { + label = item.l; + } + var li = $("
  • ").appendTo(ul); + var div = $("
    ").appendTo(li); + if (item.category === catSearchTags) { + if (item.d) { + div.html(label + " (" + item.h + ")
    " + + item.d + "
    "); + } else { + div.html(label + " (" + item.h + ")"); + } + } else { + div.html(label); + } + return li; + } +}); +$(function() { + $("#search").catcomplete({ + minLength: 1, + delay: 100, + source: function(request, response) { + var result = new Array(); + var presult = new Array(); + var tresult = new Array(); + var mresult = new Array(); + var tgresult = new Array(); + var secondaryresult = new Array(); + var displayCount = 0; + var exactMatcher = new RegExp("^" + $.ui.autocomplete.escapeRegex(request.term) + "$", "i"); + camelCaseRegexp = ($.ui.autocomplete.escapeRegex(request.term)).split(/(?=[A-Z])/).join("([a-z0-9_$]*?)"); + var camelCaseMatcher = new RegExp("^" + camelCaseRegexp); + secondaryMatcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), "i"); + + // Return the nested innermost name from the specified object + function nestedName(e) { + return e.l.substring(e.l.lastIndexOf(".") + 1); + } + + function concatResults(a1, a2) { + a1 = a1.concat(a2); + a2.length = 0; + return a1; + } + + if (moduleSearchIndex) { + var mdleCount = 0; + $.each(moduleSearchIndex, function(index, item) { + item.category = catModules; + if (exactMatcher.test(item.l)) { + result.push(item); + mdleCount++; + } else if (camelCaseMatcher.test(item.l)) { + result.push(item); + } else if (secondaryMatcher.test(item.l)) { + secondaryresult.push(item); + } + }); + displayCount = mdleCount; + result = concatResults(result, secondaryresult); + } + if (packageSearchIndex) { + var pCount = 0; + var pkg = ""; + $.each(packageSearchIndex, function(index, item) { + item.category = catPackages; + pkg = (item.m) + ? (item.m + "/" + item.l) + : item.l; + if (exactMatcher.test(item.l)) { + presult.push(item); + pCount++; + } else if (camelCaseMatcher.test(pkg)) { + presult.push(item); + } else if (secondaryMatcher.test(pkg)) { + secondaryresult.push(item); + } + }); + result = result.concat(concatResults(presult, secondaryresult)); + displayCount = (pCount > displayCount) ? pCount : displayCount; + } + if (typeSearchIndex) { + var tCount = 0; + $.each(typeSearchIndex, function(index, item) { + item.category = catTypes; + var s = nestedName(item); + if (exactMatcher.test(s)) { + tresult.push(item); + tCount++; + } else if (camelCaseMatcher.test(s)) { + tresult.push(item); + } else if (secondaryMatcher.test(item.p + "." + item.l)) { + secondaryresult.push(item); + } + }); + result = result.concat(concatResults(tresult, secondaryresult)); + displayCount = (tCount > displayCount) ? tCount : displayCount; + } + if (memberSearchIndex) { + var mCount = 0; + $.each(memberSearchIndex, function(index, item) { + item.category = catMembers; + var s = nestedName(item); + if (exactMatcher.test(s)) { + mresult.push(item); + mCount++; + } else if (camelCaseMatcher.test(s)) { + mresult.push(item); + } else if (secondaryMatcher.test(item.c + "." + item.l)) { + secondaryresult.push(item); + } + }); + result = result.concat(concatResults(mresult, secondaryresult)); + displayCount = (mCount > displayCount) ? mCount : displayCount; + } + if (tagSearchIndex) { + var tgCount = 0; + $.each(tagSearchIndex, function(index, item) { + item.category = catSearchTags; + if (exactMatcher.test(item.l)) { + tgresult.push(item); + tgCount++; + } else if (secondaryMatcher.test(item.l)) { + secondaryresult.push(item); + } + }); + result = result.concat(concatResults(tgresult, secondaryresult)); + displayCount = (tgCount > displayCount) ? tgCount : displayCount; + } + displayCount = (displayCount > 500) ? displayCount : 500; + var counter = function() { + var count = {Modules: 0, Packages: 0, Types: 0, Members: 0, SearchTags: 0}; + var f = function(item) { + count[item.category] += 1; + return (count[item.category] <= displayCount); + }; + return f; + }(); + response(result.filter(counter)); + }, + response: function(event, ui) { + if (!ui.content.length) { + ui.content.push(noResult); + } else { + $("#search").empty(); + } + }, + autoFocus: true, + position: { + collision: "flip" + }, + select: function(event, ui) { + if (ui.item.l !== noResult.l) { + var url = getURLPrefix(ui); + if (ui.item.category === catModules) { + if (useModuleDirectories) { + url += "module-summary.html"; + } else { + url = ui.item.l + "-summary.html"; + } + } else if (ui.item.category === catPackages) { + if (ui.item.url) { + url = ui.item.url; + } else { + url += ui.item.l.replace(/\./g, '/') + "/package-summary.html"; + } + } else if (ui.item.category === catTypes) { + if (ui.item.url) { + url = ui.item.url; + } else if (ui.item.p === "") { + url += ui.item.l + ".html"; + } else { + url += ui.item.p.replace(/\./g, '/') + "/" + ui.item.l + ".html"; + } + } else if (ui.item.category === catMembers) { + if (ui.item.p === "") { + url += ui.item.c + ".html" + "#"; + } else { + url += ui.item.p.replace(/\./g, '/') + "/" + ui.item.c + ".html" + "#"; + } + if (ui.item.url) { + url += ui.item.url; + } else { + url += ui.item.l; + } + } else if (ui.item.category === catSearchTags) { + url += ui.item.u; + } + if (top !== window) { + parent.classFrame.location = pathtoroot + url; + } else { + window.location.href = pathtoroot + url; + } + $("#search").focus(); + } + } + }); +}); diff --git a/0.7.0/_static/javadoc/stylesheet.css b/0.7.0/_static/javadoc/stylesheet.css new file mode 100644 index 0000000..de945ed --- /dev/null +++ b/0.7.0/_static/javadoc/stylesheet.css @@ -0,0 +1,910 @@ +/* + * Javadoc style sheet + */ + +@import url('resources/fonts/dejavu.css'); + +/* + * Styles for individual HTML elements. + * + * These are styles that are specific to individual HTML elements. Changing them affects the style of a particular + * HTML element throughout the page. + */ + +body { + background-color:#ffffff; + color:#353833; + font-family:'DejaVu Sans', Arial, Helvetica, sans-serif; + font-size:14px; + margin:0; + padding:0; + height:100%; + width:100%; +} +iframe { + margin:0; + padding:0; + height:100%; + width:100%; + overflow-y:scroll; + border:none; +} +a:link, a:visited { + text-decoration:none; + color:#4A6782; +} +a[href]:hover, a[href]:focus { + text-decoration:none; + color:#bb7a2a; +} +a[name] { + color:#353833; +} +a[name]:before, a[name]:target, a[id]:before, a[id]:target { + content:""; + display:inline-block; + position:relative; + padding-top:129px; + margin-top:-129px; +} +pre { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; +} +h1 { + font-size:20px; +} +h2 { + font-size:18px; +} +h3 { + font-size:16px; + font-style:italic; +} +h4 { + font-size:13px; +} +h5 { + font-size:12px; +} +h6 { + font-size:11px; +} +ul { + list-style-type:disc; +} +code, tt { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; + margin-top:8px; + line-height:1.4em; +} +dt code { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; +} +table tr td dt code { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + vertical-align:top; + padding-top:4px; +} +sup { + font-size:8px; +} + +/* + * Styles for HTML generated by javadoc. + * + * These are style classes that are used by the standard doclet to generate HTML documentation. + */ + +/* + * Styles for document title and copyright. + */ +.clear { + clear:both; + height:0px; + overflow:hidden; +} +.aboutLanguage { + float:right; + padding:0px 21px; + font-size:11px; + z-index:200; + margin-top:-9px; +} +.legalCopy { + margin-left:.5em; +} +.bar a, .bar a:link, .bar a:visited, .bar a:active { + color:#FFFFFF; + text-decoration:none; +} +.bar a:hover, .bar a:focus { + color:#bb7a2a; +} +.tab { + background-color:#0066FF; + color:#ffffff; + padding:8px; + width:5em; + font-weight:bold; +} +/* + * Styles for navigation bar. + */ +.bar { + background-color:#4D7A97; + color:#FFFFFF; + padding:.8em .5em .4em .8em; + height:auto;/*height:1.8em;*/ + font-size:11px; + margin:0; +} +.navPadding { + padding-top: 107px; +} +.fixedNav { + position:fixed; + width:100%; + z-index:999; + background-color:#ffffff; +} +.topNav { + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; +} +.bottomNav { + margin-top:10px; + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; +} +.subNav { + background-color:#dee3e9; + float:left; + width:100%; + overflow:hidden; + font-size:12px; +} +.subNav div { + clear:left; + float:left; + padding:0 0 5px 6px; + text-transform:uppercase; +} +ul.navList, ul.subNavList { + float:left; + margin:0 25px 0 0; + padding:0; +} +ul.navList li{ + list-style:none; + float:left; + padding: 5px 6px; + text-transform:uppercase; +} +ul.navListSearch { + float:right; + margin:0 0 0 0; + padding:0; +} +ul.navListSearch li { + list-style:none; + float:right; + padding: 5px 6px; + text-transform:uppercase; +} +ul.navListSearch li label { + position:relative; + right:-16px; +} +ul.subNavList li { + list-style:none; + float:left; +} +.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { + color:#FFFFFF; + text-decoration:none; + text-transform:uppercase; +} +.topNav a:hover, .bottomNav a:hover { + text-decoration:none; + color:#bb7a2a; + text-transform:uppercase; +} +.navBarCell1Rev { + background-color:#F8981D; + color:#253441; + margin: auto 5px; +} +.skipNav { + position:absolute; + top:auto; + left:-9999px; + overflow:hidden; +} +/* + * Styles for page header and footer. + */ +.header, .footer { + clear:both; + margin:0 20px; + padding:5px 0 0 0; +} +.indexNav { + position:relative; + font-size:12px; + background-color:#dee3e9; +} +.indexNav ul { + margin-top:0; + padding:5px; +} +.indexNav ul li { + display:inline; + list-style-type:none; + padding-right:10px; + text-transform:uppercase; +} +.indexNav h1 { + font-size:13px; +} +.title { + color:#2c4557; + margin:10px 0; +} +.subTitle { + margin:5px 0 0 0; +} +.header ul { + margin:0 0 15px 0; + padding:0; +} +.footer ul { + margin:20px 0 5px 0; +} +.header ul li, .footer ul li { + list-style:none; + font-size:13px; +} +/* + * Styles for headings. + */ +div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; +} +ul.blockList ul.blockList ul.blockList li.blockList h3 { + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; +} +ul.blockList ul.blockList li.blockList h3 { + padding:0; + margin:15px 0; +} +ul.blockList li.blockList h2 { + padding:0px 0 20px 0; +} +/* + * Styles for page layout containers. + */ +.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer, +.allClassesContainer, .allPackagesContainer { + clear:both; + padding:10px 20px; + position:relative; +} +.indexContainer { + margin:10px; + position:relative; + font-size:12px; +} +.indexContainer h2 { + font-size:13px; + padding:0 0 3px 0; +} +.indexContainer ul { + margin:0; + padding:0; +} +.indexContainer ul li { + list-style:none; + padding-top:2px; +} +.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { + font-size:12px; + font-weight:bold; + margin:10px 0 0 0; + color:#4E4E4E; +} +.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { + margin:5px 0 10px 0px; + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; +} +.serializedFormContainer dl.nameValue dt { + margin-left:1px; + font-size:1.1em; + display:inline; + font-weight:bold; +} +.serializedFormContainer dl.nameValue dd { + margin:0 0 0 1px; + font-size:1.1em; + display:inline; +} +/* + * Styles for lists. + */ +li.circle { + list-style:circle; +} +ul.horizontal li { + display:inline; + font-size:0.9em; +} +ul.inheritance { + margin:0; + padding:0; +} +ul.inheritance li { + display:inline; + list-style:none; +} +ul.inheritance li ul.inheritance { + margin-left:15px; + padding-left:15px; + padding-top:1px; +} +ul.blockList, ul.blockListLast { + margin:10px 0 10px 0; + padding:0; +} +ul.blockList li.blockList, ul.blockListLast li.blockList { + list-style:none; + margin-bottom:15px; + line-height:1.4; +} +ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { + padding:0px 20px 5px 10px; + border:1px solid #ededed; + background-color:#f8f8f8; +} +ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { + padding:0 0 5px 8px; + background-color:#ffffff; + border:none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { + margin-left:0; + padding-left:0; + padding-bottom:15px; + border:none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { + list-style:none; + border-bottom:none; + padding-bottom:0; +} +table tr td dl, table tr td dl dt, table tr td dl dd { + margin-top:0; + margin-bottom:1px; +} +/* + * Styles for tables. + */ +.overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary, +.requiresSummary, .packagesSummary, .providesSummary, .usesSummary { + width:100%; + border-spacing:0; + border-left:1px solid #EEE; + border-right:1px solid #EEE; + border-bottom:1px solid #EEE; +} +.overviewSummary, .memberSummary, .requiresSummary, .packagesSummary, .providesSummary, .usesSummary { + padding:0px; +} +.overviewSummary caption, .memberSummary caption, .typeSummary caption, +.useSummary caption, .constantsSummary caption, .deprecatedSummary caption, +.requiresSummary caption, .packagesSummary caption, .providesSummary caption, .usesSummary caption { + position:relative; + text-align:left; + background-repeat:no-repeat; + color:#253441; + font-weight:bold; + clear:none; + overflow:hidden; + padding:0px; + padding-top:10px; + padding-left:1px; + margin:0px; + white-space:pre; +} +.constantsSummary caption a:link, .constantsSummary caption a:visited, +.useSummary caption a:link, .useSummary caption a:visited { + color:#1f389c; +} +.overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link, +.deprecatedSummary caption a:link, +.requiresSummary caption a:link, .packagesSummary caption a:link, .providesSummary caption a:link, +.usesSummary caption a:link, +.overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover, +.useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover, +.requiresSummary caption a:hover, .packagesSummary caption a:hover, .providesSummary caption a:hover, +.usesSummary caption a:hover, +.overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active, +.useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active, +.requiresSummary caption a:active, .packagesSummary caption a:active, .providesSummary caption a:active, +.usesSummary caption a:active, +.overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited, +.deprecatedSummary caption a:visited, +.requiresSummary caption a:visited, .packagesSummary caption a:visited, .providesSummary caption a:visited, +.usesSummary caption a:visited { + color:#FFFFFF; +} +.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span, +.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span, +.requiresSummary caption span, .packagesSummary caption span, .providesSummary caption span, +.usesSummary caption span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + padding-bottom:7px; + display:inline-block; + float:left; + background-color:#F8981D; + border: none; + height:16px; +} +.memberSummary caption span.activeTableTab span, .packagesSummary caption span.activeTableTab span, +.overviewSummary caption span.activeTableTab span, .typeSummary caption span.activeTableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#F8981D; + height:16px; +} +.memberSummary caption span.tableTab span, .packagesSummary caption span.tableTab span, +.overviewSummary caption span.tableTab span, .typeSummary caption span.tableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#4D7A97; + height:16px; +} +.memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab, +.packagesSummary caption span.tableTab, .packagesSummary caption span.activeTableTab, +.overviewSummary caption span.tableTab, .overviewSummary caption span.activeTableTab, +.typeSummary caption span.tableTab, .typeSummary caption span.activeTableTab { + padding-top:0px; + padding-left:0px; + padding-right:0px; + background-image:none; + float:none; + display:inline; +} +.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd, +.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd, +.requiresSummary .tabEnd, .packagesSummary .tabEnd, .providesSummary .tabEnd, .usesSummary .tabEnd { + display:none; + width:5px; + position:relative; + float:left; + background-color:#F8981D; +} +.memberSummary .activeTableTab .tabEnd, .packagesSummary .activeTableTab .tabEnd, +.overviewSummary .activeTableTab .tabEnd, .typeSummary .activeTableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + float:left; + background-color:#F8981D; +} +.memberSummary .tableTab .tabEnd, .packagesSummary .tableTab .tabEnd, +.overviewSummary .tableTab .tabEnd, .typeSummary .tableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + background-color:#4D7A97; + float:left; +} +.rowColor th, .altColor th { + font-weight:normal; +} +.overviewSummary td, .memberSummary td, .typeSummary td, +.useSummary td, .constantsSummary td, .deprecatedSummary td, +.requiresSummary td, .packagesSummary td, .providesSummary td, .usesSummary td { + text-align:left; + padding:0px 0px 12px 10px; +} +th.colFirst, th.colSecond, th.colLast, th.colConstructorName, th.colDeprecatedItemName, .useSummary th, +.constantsSummary th, .packagesSummary th, td.colFirst, td.colSecond, td.colLast, .useSummary td, +.constantsSummary td { + vertical-align:top; + padding-right:0px; + padding-top:8px; + padding-bottom:3px; +} +th.colFirst, th.colSecond, th.colLast, th.colConstructorName, th.colDeprecatedItemName, .constantsSummary th, +.packagesSummary th { + background:#dee3e9; + text-align:left; + padding:8px 3px 3px 7px; +} +td.colFirst, th.colFirst { + font-size:13px; +} +td.colSecond, th.colSecond, td.colLast, th.colConstructorName, th.colDeprecatedItemName, th.colLast { + font-size:13px; +} +.constantsSummary th, .packagesSummary th { + font-size:13px; +} +.providesSummary th.colFirst, .providesSummary th.colLast, .providesSummary td.colFirst, +.providesSummary td.colLast { + white-space:normal; + font-size:13px; +} +.overviewSummary td.colFirst, .overviewSummary th.colFirst, +.requiresSummary td.colFirst, .requiresSummary th.colFirst, +.packagesSummary td.colFirst, .packagesSummary td.colSecond, .packagesSummary th.colFirst, .packagesSummary th, +.usesSummary td.colFirst, .usesSummary th.colFirst, +.providesSummary td.colFirst, .providesSummary th.colFirst, +.memberSummary td.colFirst, .memberSummary th.colFirst, +.memberSummary td.colSecond, .memberSummary th.colSecond, .memberSummary th.colConstructorName, +.typeSummary td.colFirst, .typeSummary th.colFirst { + vertical-align:top; +} +.packagesSummary th.colLast, .packagesSummary td.colLast { + white-space:normal; +} +td.colFirst a:link, td.colFirst a:visited, +td.colSecond a:link, td.colSecond a:visited, +th.colFirst a:link, th.colFirst a:visited, +th.colSecond a:link, th.colSecond a:visited, +th.colConstructorName a:link, th.colConstructorName a:visited, +th.colDeprecatedItemName a:link, th.colDeprecatedItemName a:visited, +.constantValuesContainer td a:link, .constantValuesContainer td a:visited, +.allClassesContainer td a:link, .allClassesContainer td a:visited, +.allPackagesContainer td a:link, .allPackagesContainer td a:visited { + font-weight:bold; +} +.tableSubHeadingColor { + background-color:#EEEEFF; +} +.altColor, .altColor th { + background-color:#FFFFFF; +} +.rowColor, .rowColor th { + background-color:#EEEEEF; +} +/* + * Styles for contents. + */ +.description pre { + margin-top:0; +} +.deprecatedContent { + margin:0; + padding:10px 0; +} +.docSummary { + padding:0; +} +ul.blockList ul.blockList ul.blockList li.blockList h3 { + font-style:normal; +} +div.block { + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; +} +td.colLast div { + padding-top:0px; +} +td.colLast a { + padding-bottom:3px; +} +/* + * Styles for formatting effect. + */ +.sourceLineNo { + color:green; + padding:0 30px 0 0; +} +h1.hidden { + visibility:hidden; + overflow:hidden; + font-size:10px; +} +.block { + display:block; + margin:3px 10px 2px 0px; + color:#474747; +} +.deprecatedLabel, .descfrmTypeLabel, .implementationLabel, .memberNameLabel, .memberNameLink, +.moduleLabelInPackage, .moduleLabelInType, .overrideSpecifyLabel, .packageLabelInType, +.packageHierarchyLabel, .paramLabel, .returnLabel, .seeLabel, .simpleTagLabel, +.throwsLabel, .typeNameLabel, .typeNameLink, .searchTagLink { + font-weight:bold; +} +.deprecationComment, .emphasizedPhrase, .interfaceName { + font-style:italic; +} +.deprecationBlock { + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; + border-style:solid; + border-width:thin; + border-radius:10px; + padding:10px; + margin-bottom:10px; + margin-right:10px; + display:inline-block; +} +div.block div.deprecationComment, div.block div.block span.emphasizedPhrase, +div.block div.block span.interfaceName { + font-style:normal; +} +div.contentContainer ul.blockList li.blockList h2 { + padding-bottom:0px; +} +/* + * Styles for IFRAME. + */ +.mainContainer { + margin:0 auto; + padding:0; + height:100%; + width:100%; + position:fixed; + top:0; + left:0; +} +.leftContainer { + height:100%; + position:fixed; + width:320px; +} +.leftTop { + position:relative; + float:left; + width:315px; + top:0; + left:0; + height:30%; + border-right:6px solid #ccc; + border-bottom:6px solid #ccc; +} +.leftBottom { + position:relative; + float:left; + width:315px; + bottom:0; + left:0; + height:70%; + border-right:6px solid #ccc; + border-top:1px solid #000; +} +.rightContainer { + position:absolute; + left:320px; + top:0; + bottom:0; + height:100%; + right:0; + border-left:1px solid #000; +} +.rightIframe { + margin:0; + padding:0; + height:100%; + right:30px; + width:100%; + overflow:visible; + margin-bottom:30px; +} +/* + * Styles specific to HTML5 elements. + */ +main, nav, header, footer, section { + display:block; +} +/* + * Styles for javadoc search. + */ +.ui-autocomplete-category { + font-weight:bold; + font-size:15px; + padding:7px 0 7px 3px; + background-color:#4D7A97; + color:#FFFFFF; +} +.resultItem { + font-size:13px; +} +.ui-autocomplete { + max-height:85%; + max-width:65%; + overflow-y:scroll; + overflow-x:scroll; + white-space:nowrap; + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); +} +ul.ui-autocomplete { + position:fixed; + z-index:999999; + background-color: #FFFFFF; +} +ul.ui-autocomplete li { + float:left; + clear:both; + width:100%; +} +.resultHighlight { + font-weight:bold; +} +.ui-autocomplete .result-item { + font-size: inherit; +} +#search { + background-image:url('resources/glass.png'); + background-size:13px; + background-repeat:no-repeat; + background-position:2px 3px; + padding-left:20px; + position:relative; + right:-18px; +} +#reset { + background-color: rgb(255,255,255); + background-image:url('resources/x.png'); + background-position:center; + background-repeat:no-repeat; + background-size:12px; + border:0 none; + width:16px; + height:17px; + position:relative; + left:-4px; + top:-4px; + font-size:0px; +} +.watermark { + color:#545454; +} +.searchTagDescResult { + font-style:italic; + font-size:11px; +} +.searchTagHolderResult { + font-style:italic; + font-size:12px; +} +.searchTagResult:before, .searchTagResult:target { + color:red; +} +.moduleGraph span { + display:none; + position:absolute; +} +.moduleGraph:hover span { + display:block; + margin: -100px 0 0 100px; + z-index: 1; +} +.methodSignature { + white-space:normal; +} + +/* + * Styles for user-provided tables. + * + * borderless: + * No borders, vertical margins, styled caption. + * This style is provided for use with existing doc comments. + * In general, borderless tables should not be used for layout purposes. + * + * plain: + * Plain borders around table and cells, vertical margins, styled caption. + * Best for small tables or for complex tables for tables with cells that span + * rows and columns, when the "striped" style does not work well. + * + * striped: + * Borders around the table and vertical borders between cells, striped rows, + * vertical margins, styled caption. + * Best for tables that have a header row, and a body containing a series of simple rows. + */ + +table.borderless, +table.plain, +table.striped { + margin-top: 10px; + margin-bottom: 10px; +} +table.borderless > caption, +table.plain > caption, +table.striped > caption { + font-weight: bold; + font-size: smaller; +} +table.borderless th, table.borderless td, +table.plain th, table.plain td, +table.striped th, table.striped td { + padding: 2px 5px; +} +table.borderless, +table.borderless > thead > tr > th, table.borderless > tbody > tr > th, table.borderless > tr > th, +table.borderless > thead > tr > td, table.borderless > tbody > tr > td, table.borderless > tr > td { + border: none; +} +table.borderless > thead > tr, table.borderless > tbody > tr, table.borderless > tr { + background-color: transparent; +} +table.plain { + border-collapse: collapse; + border: 1px solid black; +} +table.plain > thead > tr, table.plain > tbody tr, table.plain > tr { + background-color: transparent; +} +table.plain > thead > tr > th, table.plain > tbody > tr > th, table.plain > tr > th, +table.plain > thead > tr > td, table.plain > tbody > tr > td, table.plain > tr > td { + border: 1px solid black; +} +table.striped { + border-collapse: collapse; + border: 1px solid black; +} +table.striped > thead { + background-color: #E3E3E3; +} +table.striped > thead > tr > th, table.striped > thead > tr > td { + border: 1px solid black; +} +table.striped > tbody > tr:nth-child(even) { + background-color: #EEE +} +table.striped > tbody > tr:nth-child(odd) { + background-color: #FFF +} +table.striped > tbody > tr > th, table.striped > tbody > tr > td { + border-left: 1px solid black; + border-right: 1px solid black; +} +table.striped > tbody > tr > th { + font-weight: normal; +} diff --git a/0.7.0/_static/javadoc/type-search-index.js b/0.7.0/_static/javadoc/type-search-index.js new file mode 100644 index 0000000..c96602a --- /dev/null +++ b/0.7.0/_static/javadoc/type-search-index.js @@ -0,0 +1 @@ +typeSearchIndex = [{"l":"All Classes","url":"allclasses-index.html"},{"p":"org.hansken.plugin.extraction.util","l":"ArgChecks"},{"p":"org.hansken.plugin.extraction.api","l":"Author"},{"p":"org.hansken.plugin.extraction.api","l":"BaseExtractionPlugin"},{"p":"org.hansken.plugin.extraction.api","l":"BatchSearchResult"},{"p":"org.hansken.plugin.extraction.api","l":"Author.Builder"},{"p":"org.hansken.plugin.extraction.api","l":"PluginInfo.Builder"},{"p":"org.hansken.plugin.extraction.api","l":"PluginResources.Builder"},{"p":"org.hansken.plugin.extraction.api.transformations","l":"RangedDataTransformation.Builder"},{"p":"org.hansken.plugin.extraction.api","l":"DataContext"},{"p":"org.hansken.plugin.extraction.api.transformations","l":"DataRange"},{"p":"org.hansken.plugin.extraction.api.transformations","l":"DataTransformation"},{"p":"org.hansken.plugin.extraction.api","l":"DataWriter"},{"p":"org.hansken.plugin.extraction.api","l":"DeferredExtractionPlugin"},{"p":"org.hansken.plugin.extraction.api","l":"ExtractionPlugin"},{"p":"org.hansken.plugin.extraction.api","l":"ImmutableTrace"},{"p":"org.hansken.plugin.extraction.api","l":"LatLong"},{"p":"org.hansken.plugin.extraction.api","l":"MaturityLevel"},{"p":"org.hansken.plugin.extraction.api","l":"MetaExtractionPlugin"},{"p":"org.hansken.plugin.extraction.api","l":"PluginId"},{"p":"org.hansken.plugin.extraction.api","l":"PluginInfo"},{"p":"org.hansken.plugin.extraction.api","l":"PluginResources"},{"p":"org.hansken.plugin.extraction.api","l":"PluginType"},{"p":"org.hansken.plugin.extraction.api","l":"RandomAccessData"},{"p":"org.hansken.plugin.extraction.api.transformations","l":"RangedDataTransformation"},{"p":"org.hansken.plugin.extraction.api","l":"SearchResult"},{"p":"org.hansken.plugin.extraction.api","l":"SearchTrace"},{"p":"org.hansken.plugin.extraction.util","l":"ThrowingConsumer"},{"p":"org.hansken.plugin.extraction.api","l":"Trace"},{"p":"org.hansken.plugin.extraction.api","l":"Trace.Tracelet"},{"p":"org.hansken.plugin.extraction.api","l":"Trace.TraceletBuilder"},{"p":"org.hansken.plugin.extraction.api","l":"Trace.TraceletProperty"},{"p":"org.hansken.plugin.extraction.api","l":"TraceSearcher"},{"p":"org.hansken.plugin.extraction.api","l":"Vector"}] \ No newline at end of file diff --git a/0.7.0/_static/javadoc/type-search-index.zip b/0.7.0/_static/javadoc/type-search-index.zip new file mode 100644 index 0000000..a10cf19 Binary files /dev/null and b/0.7.0/_static/javadoc/type-search-index.zip differ diff --git a/0.7.0/_static/jquery.js b/0.7.0/_static/jquery.js new file mode 100644 index 0000000..c4c6022 --- /dev/null +++ b/0.7.0/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
    ",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/0.7.0/_static/js/html5shiv.min.js b/0.7.0/_static/js/html5shiv.min.js new file mode 100644 index 0000000..cd1c674 --- /dev/null +++ b/0.7.0/_static/js/html5shiv.min.js @@ -0,0 +1,4 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/0.7.0/_static/js/theme.js b/0.7.0/_static/js/theme.js new file mode 100644 index 0000000..1fddb6e --- /dev/null +++ b/0.7.0/_static/js/theme.js @@ -0,0 +1 @@ +!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
    "),n("table.docutils.footnote").wrap("
    "),n("table.docutils.citation").wrap("
    "),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/0.7.0/_static/minus.png b/0.7.0/_static/minus.png new file mode 100644 index 0000000..d96755f Binary files /dev/null and b/0.7.0/_static/minus.png differ diff --git a/0.7.0/_static/plus.png b/0.7.0/_static/plus.png new file mode 100644 index 0000000..7107cec Binary files /dev/null and b/0.7.0/_static/plus.png differ diff --git a/0.7.0/_static/pygments.css b/0.7.0/_static/pygments.css new file mode 100644 index 0000000..7a18115 --- /dev/null +++ b/0.7.0/_static/pygments.css @@ -0,0 +1,74 @@ +pre { line-height: 125%; } +td.linenos .normal { color: #666666; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: #666666; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f0f0f0; } +.highlight .c { color: #60a0b0; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #60a0b0; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .cpf { color: #60a0b0; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #40a070 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #40a070 } /* Literal.Number.Bin */ +.highlight .mf { color: #40a070 } /* Literal.Number.Float */ +.highlight .mh { color: #40a070 } /* Literal.Number.Hex */ +.highlight .mi { color: #40a070 } /* Literal.Number.Integer */ +.highlight .mo { color: #40a070 } /* Literal.Number.Oct */ +.highlight .sa { color: #4070a0 } /* Literal.String.Affix */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #06287e } /* Name.Function.Magic */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ +.highlight .il { color: #40a070 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/0.7.0/_static/searchtools.js b/0.7.0/_static/searchtools.js new file mode 100644 index 0000000..97d56a7 --- /dev/null +++ b/0.7.0/_static/searchtools.js @@ -0,0 +1,566 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = docUrlRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = docUrlRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms) + ); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + `Search finished, found ${resultCount} page(s) matching the search query.` + ); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent !== undefined) return docContent.textContent; + console.warn( + "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + /** + * execute search (requires search index to be loaded) + */ + query: (query) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + // array of [docname, title, anchor, descr, score, filename] + let results = []; + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + results.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id] of foundEntries) { + let score = Math.round(100 * queryLower.length / entry.length) + results.push([ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // lookup as object + objectTerms.forEach((term) => + results.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); + + // now sort the results by score (in opposite order of appearance, since the + // display function below uses pop() to retrieve items) and then + // alphabetically + results.sort((a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; + }); + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + results = results.reverse(); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord) && !terms[word]) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord) && !titleTerms[word]) + arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); + }); + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); + else fileMap.set(file, [word]); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords) => { + const text = Search.htmlToText(htmlText); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/0.7.0/_static/sphinx_highlight.js b/0.7.0/_static/sphinx_highlight.js new file mode 100644 index 0000000..aae669d --- /dev/null +++ b/0.7.0/_static/sphinx_highlight.js @@ -0,0 +1,144 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + parent.insertBefore( + span, + parent.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(SphinxHighlight.highlightSearchWords); +_ready(SphinxHighlight.initEscapeListener); diff --git a/0.7.0/_static/wider_pages.css b/0.7.0/_static/wider_pages.css new file mode 100644 index 0000000..bf5fc66 --- /dev/null +++ b/0.7.0/_static/wider_pages.css @@ -0,0 +1,3 @@ +.wy-nav-content { + max-width: 1200px !important; +} diff --git a/0.7.0/changes.html b/0.7.0/changes.html new file mode 100644 index 0000000..d0e50ca --- /dev/null +++ b/0.7.0/changes.html @@ -0,0 +1,605 @@ + + + + + + + Changelog — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Changelog

    +

    The following page lists all (technical) changes in the extraction plugin SDK.

    +

    Programming language specific API changes are described in more detail on API changelog pages. +These pages list new API functionalities, and describe how to update your plugins when API changes are in order. +For the API changelog pages see:

    + +
    +

    Release-0.7.0 +

    +
      +
    • HANSKEN-18677: Added HQL-Lite auto-escaping & fixed fullmatch wildcard support

    • +
    • HANSKEN-19179: Update all project dependencies

    • +
    • HANSKEN-19148: Improve build_plugin and test framework output (by passing subprocess output directly to the terminal)

    • +
    • HANSKEN-19076: Exposed API version through the ExtractionPluginClient

    • +
    • HANSKEN-18830: Added the Hansken AIO DebugExtractionPluginTool to the docs

    • +
    • HANSKEN-19065: Update link to extraction plugin examples (now hosted on Github)

    • +
    • HANSKEN-19064: Allow plugins to import local modules (Python)

    • +
    • HANSKEN-17675: Enable writing streaming data from Python

    • +
    • HANSKEN-18982: Improve error messages for client-side exceptions caught by gRPC (also for deferred plugins)

    • +
    +
    +
    +

    Release-0.6.3

    +
      +
    • HANSKEN-18915: Update all project dependencies

    • +
    • HANSKEN-18907: Upgrade to antlr 4.9.3

    • +
    • HANSKEN-18883: Allow writing multiple datastreams to a trace concurrently

    • +
    • HANSKEN-18673: Improve error messages for client-side exceptions caught by gRPC

    • +
    • HANSKEN-18517: Improve Python documentation for creating nested traces

    • +
    • HANSKEN-18400: Building docs with tox uses docutils 0.18.1

    • +
    • HANSKEN-17556: Enables writing to traces/child traces out of order

    • +
    +
    +
    +

    Release-0.6.2

    +
      +
    • HANSKEN-17692: [EXPERIMENTAL] Add Python support for previews, and extend test-framework to support them as well

    • +
    • HANSKEN-17742: Raise FileNotFoundError instead of logging an error when the plugin’s file does not exist

    • +
    • HANSKEN-17786: Update grpc dependencies to 1.48.2 to fix CVE-2022-3509

    • +
    • HANSKEN-17636: Improve explanation on “Match on specific datastream type”

    • +
    • HANSKEN-17672: Add a nice JB cartoon to the SDK docs landing page

    • +
    • HANSKEN-17502: Fix org.hansken.plugin-info.id label for java plugins

    • +
    • HANSKEN-17460: Update Flits to 3.7.1

    • +
    +
    +
    +

    Release-0.6.1

    +
      +
    • HANSKEN-17265: Added a parameter to build_plugin.py to extend the docker command for proxy settings

    • +
    • HANSKEN-17264: Remove PortUtil and expose listening ports

    • +
    • HANSKEN-17276: Move external getting started to the SDK documentation

    • +
    • HANSKEN-17274: Fix broken external links to Hansken website

    • +
    • HANSKEN-17278: Add url and license to SDK Python package info

    • +
    • HANSKEN-17203: Publish new SDK releases to maven central (Java)

    • +
    • HANSKEN-17277: Rearrange code snippets documentation

    • +
    • HANSKEN-17273: Update Checkstyle

    • +
    • HANSKEN-17214: Fix issues from static code analysis

    • +
    +
    +
    +

    Release-0.6.0

    +
      +
    • HANSKEN-17196: Downgrade API compatability level to 0.5.0

    • +
    • HANSKEN-17194: Update project dependencies to latest versions

    • +
    • HANSKEN-16781: Remove the need for plugin=self and moved id to 1st arg when creating a PluginInfo in Python SDK

    • +
    • HANSKEN-17191: Add quicklinks to the documentation index

    • +
    • HANSKEN-16756: Read extraction plugin version from plugin mavens project.version

    • +
    • HANSKEN-16705: Improve apis for declaring plugin resources and querying the data context at runtime

    • +
    • HANSKEN-17151: Store Plugin info in plugin image labels for Python plugins

    • +
    • HANSKEN-16753: Store Plugin info in plugin image labels for Java plugins

    • +
    • HANSKEN-17178: Updated Python tracelet documentation to contain a working example

    • +
    +
    +
    +

    Release-0.5.1

    +
      +
    • HANSKEN-17141: Fixed $data.type=... matcher when using run_with_hanskenpy (Python)

    • +
    • HANSKEN-16908: Added gRPC health service for Python Extraction Plugins

    • +
    • HANSKEN-17138: Avoid multiple plugins running on the same hostname:port (Python)

    • +
    • HANSKEN-14755: Use Python test framework wrapper with manually started plugins

    • +
    • HANSKEN-16905: Added gRPC health service for Java Extraction Plugins

    • +
    • HANSKEN-16901: Exclude old guava version to be able to run RemoteExtractionPluginFlits from Intellij

    • +
    • HANSKEN-16900: TestTraceSearcher will now return searched traces in natural sorted order of their file names

    • +
    • HANSKEN-16725: Improve tracelet api in Python SDK

    • +
    • HANSKEN-16764: Corrected ‘Adding data to traces’ code snippets

    • +
    • HANSKEN-16704: Use Python’s dataclasses where applicable

    • +
    • HANSKEN-17064: Improve runtime packing type checks to allow Sequence / Mapping compatible values

    • +
    +
    +
    +

    Release-0.5.0

    +
      +
    • HANSKEN-16638: Improve tracelet api in Java SDK

    • +
    • HANSKEN-16576: Support vector data type in Python SDK

    • +
    • HANSKEN-16707: Update build dependencies, build and test for Python 3.10

    • +
    • HANSKEN-16575: Support vector data type in Java SDK

    • +
    • HANSKEN-16574: Support vector data type in common SDK

    • +
    +
    +
    +

    Release-0.4.14

    +
      +
    • HANSKEN-16632: Fix execution of meta-extraction plugins with hansken.py runner which failed with an ‘expected $data in matcher’ error

    • +
    • HANSKEN-16634: Allow forward compatibility with Hansken by introducing an GRPC API version

    • +
    • HANSKEN-16489: Let build pipeline publish Java artifacts to community

    • +
    • HANSKEN-16558: Serve SDK test framework errors in a more developer-friendly way

    • +
    • HANSKEN-16489: Removed incompatibility warning for All In One with Hansken.py

    • +
    • HANSKEN-16403: Fixed running markdownlint with tox -e markdownlint

    • +
    • HANSKEN-16268: Added * value support to the HQL-Lite term matcher, improved documentation by using HQL default property:value instead of property=value

    • +
    • HANSKEN-16258: Fixed Jenkins build

    • +
    • HANSKEN-16257: Fixed docker stop command in test framework

    • +
    +
    +
    +

    Release-0.4.13

    +
      +
    • HANSKEN-16229: Fixed SDK documentation not correctly zipped

    • +
    • HANSKEN-16095: Documentation: added Test framework data-stream type note

    • +
    • HANSKEN-15961: Redundant plugin errors in the client set to log level debug

    • +
    • HANSKEN-16111: Updated the testframework to skip search traces when scanning input files

    • +
    • HANSKEN-16128: Updated the testframework to disallow overwriting properties, similar to Hansken

    • +
    • HANSKEN-16191: Fixed Jenkins build

      +
        +
      • commit id could not be retrieved

      • +
      • curl didn’t work due to a bad proxy

      • +
      +
    • +
    • HANSKEN-16116: Updated FLITS dependency to 3.5.2

    • +
    • HANSKEN-16105: Seeking beyond EOF for Python throws an exception

    • +
    • HANSKEN-16160: Updated remaining JUnit 4 tests to JUnit 5

    • +
    • HANSKEN-16118: Trace ids are now optional when writing automated tests using Flits

    • +
    • HANSKEN-16012: Fixed trace types were not correctly determined from property names (Python)

    • +
    • HANSKEN-16238: Changed default log level of extraction plugin server. Added command line option to increase it.

    • +
    +
    +
    +

    Release-0.4.12

    +
      +
    • HANSKEN-15857: Document HQL-lite for programmers manual

    • +
    • HANSKEN-16139: Run markdownlint in Jenkins instead of Docker

    • +
    • HANSKEN-16115: Updated log4j to version 2.16.0 due CVE-2021-44228

    • +
    • HANSKEN-15651: Added documentation on test files

    • +
    • HANSKEN-16001: Improved Python exceptions for better readability

    • +
    • HANSKEN-16044: Fixed documentation being unstashed to wrong directory on Jenkins

    • +
    • HANSKEN-14586: Documentation: added code snippets for adding a Datastream

    • +
    • HANSKEN-16038: Hansken.py: fix deferred tools

    • +
    • HANSKEN-15653: Added documentation on use with Hansken.py

    • +
    • HANSKEN-15801: Change build agent in Jenkins file

    • +
    • HANSKEN-16090: Fix for documentation build (m2r2 requires mistune < 2.0.0)

    • +
    • HANSKEN-15771: Document trace properties that mismatch the trace model

    • +
    +
    +
    +

    Release-0.4.11

    +
      +
    • HANSKEN-16048: Fix documentation was not released because the documentation zip was located in docs/_build/docs

    • +
    +
    +
    +

    Release-0.4.10

    +
      +
    • HANSKEN-16037: Repair release

    • +
    +
    +
    +

    Release-0.4.9

    +
      +
    • HANSKEN-15858: Add a link to the Extraction Plugin SDK API Javadocs

    • +
    • HANSKEN-15656: Documentation: expanded FAQ

    • +
    • HANSKEN-15993: Added isVerboseLoggingEnabled() method to ExtractionPluginFlits to enable verbose logging if desired

    • +
    • HANSKEN-15801: Change build agent in Jenkins file.

    • +
    • HANSKEN-15766: Documentation: Mention the Getting Started guide on gitlab

    • +
    • HANSKEN-15765: Added documentation on the ‘Anatomy of a plugin’

    • +
    • HANSKEN-15772: Added documentation on tracelets

    • +
    • HANSKEN-15770: Added debug documentation (Java and Python)

    • +
    • HANSKEN-15773: Added documentation linter (markdownlint)

    • +
    • HANSKEN-15964: Added –verbose option to Python test_plugin runner

    • +
    • HANSKEN-15913: Use traceUid instead of traceId to read data

    • +
    +
    +
    +

    Release-0.4.8

    +
      +
    • HANSKEN-15668: Bugfix: prevent search from crashing plugin if results contain traces from different images

    • +
    +
    +
    +

    Release-0.4.7

    +
      +
    • HANSKEN-15871: Fix for RpcUnixTime & RpcZonedDateTime that were parsed incorrectly in Python

    • +
    • HANSKEN-15745: Document naming convention and added convienience method id(domain, category, name) to the PluginInfoBuilder

    • +
    • HANSKEN-15790: Change dependency pinning policy

    • +
    • HANSKEN-15790: Fix typing issues discovered by upgrading mypy-protobuf

    • +
    • HANSKEN-15650: Added documentation on general concepts

    • +
    • HANSKEN-15743: Fix Test framework should not limit input files to 2G

    • +
    • HANSKEN-15846: Improved hansken.py matcher, instead of $data.type all $data matchers are supported

    • +
    +
    +
    +

    Release-0.4.6

    +
      +
    • HANSKEN-15711: Changed PluginResources cpu/memory values to floats

    • +
    • HANSKEN-15683: Changed the trace enrichment order to ensure transformations are handled before properties

    • +
    • HANSKEN-15589: Extend PluginInfo with the ability to specify plugin resources (Python)

    • +
    • HANSKEN-15588: Renamed PluginInfo pluginResources() to resources() (Java)

    • +
    • HANSKEN-15588: Extend PluginInfo with the ability to specify plugin resources (Java)

    • +
    +
    +
    +

    Release-0.4.5

    +

    deprecated release

    +
    +
    +

    Release-0.4.4

    +

    deprecated release

    +
    +
    +

    Release-0.4.3

    +
      +
    • HANSKEN-15641: Changed PluginId to use forward-slashes instead of backward-slashes

    • +
    • HANSKEN-15607: Explicitly fail deferred extraction plugins when building PluginInfo if the number of provided iterations is invalid

    • +
    +
    +
    +

    Release-0.4.2

    +
      +
    • HANSKEN-15632: Changed PluginInfo.license field to optional, for backwards compatibility

    • +
    +
    +
    +

    Release-0.4.1

    +

    deprecated release

    +
    +
    +

    Release-0.4.0

    +
      +
    • HANSKEN-15542: Extended Python PluginInfo with a license name and id consisting of domain, category and name

    • +
    • HANSKEN-15466: Extended Java PluginInfo with a license name and id consisting of domain, category and name

    • +
    • HANSKEN-15572: Include path as a property to trace type in TraceToJson

    • +
    • HANSKEN-15034: Add deferred tools to documentation

    • +
    • HANSKEN-15541: Publish SDK documentation as maven artifact

    • +
    • HANSKEN-15562: Fix test framework does not deserialize intrinsic properties

    • +
    • HANSKEN-15365: Create a FLITS test for deferred extraction plugin

    • +
    +
    +
    +

    Release-0.3.0

    +
      +
    • HANSKEN-15527: Add api changelogs for version 0.3.0

    • +
    • HANSKEN-15393: Rename Transformation to DataTransformation and RangedTransformation to RangedDataTransformation

    • +
    • HANSKEN-15391: Add support for ranged transformations (Java only)

    • +
    • HANSKEN-15390: Add proto definitions for ranged transformations

    • +
    • HANSKEN-15392: Add support for ranged transformations (Python)

    • +
    +
    +
    +

    Release-0.2.0

    +
      +
    • HANSKEN-15520: Add dedicated page for SDK API changes per language

    • +
    • HANSKEN-15515: Move Author and MaturityLevel to PluginInfo module (Python)

    • +
    • HANSKEN-15514: Refactor ExtractionContext to DataContext (Java and Python)

    • +
    • HANSKEN-15512: Move some internals from the EP python API to runtime module

    • +
    • HANSKEN-15511: Include API documentation in SDK dev docs

    • +
    • HANSKEN-15505: Cleanup SDK: remove unused position and unrequired datatype

    • +
    • HANSKEN-15491: Add compatibility check of remote plugin with current SDK version

    • +
    • HANSKEN-15502: Update TestRandomAccessData to accept dataType parameter

    • +
    • HANSKEN-15495: Make getData() lazy and replace getAllData() with getDataTypes()

    • +
    • HANSKEN-15498: Fixed isort configuration (Python)

    • +
    • HANSKEN-15029: Limit gRPC search request count

    • +
    • HANSKEN-15274: Add Python API for deferred extraction plugins

    • +
    • HANSKEN-15035: Allow python deferred plugins to run through hansken.py.

    • +
    • HANSKEN-15288: Add a new Trace subclass for search result Traces

    • +
    • HANSKEN-15042: Research and implement requesting data through GRPC for search traces

    • +
    • HANSKEN-15139: Allow deferred extraction plugins to process traces

    • +
    • HANSKEN-15015: Add option to create deferred extraction plugins using sdk

    • +
    +
    +
    +

    Release-0.1.8

    +
      +
    • HANSKEN-15236: Read initial chunk of data along with an RPC start request (Python)

    • +
    • HANSKEN-15338: Add setting of tracelets

    • +
    • HANSKEN-15370: Fixed ArrayOutOfBoundsException when there are bytes left when prefilling the cache

    • +
    • HANSKEN-15353: Added testframework exception result validation without or on partial message(startsWith, containsInOrder)

    • +
    • HANSKEN-15231: Added caffeine block cache implementation for Java RandomAccessData

    • +
    • HANSKEN-15276: Added support for Heterogeneous Maps

    • +
    +
    +
    +

    Release-0.1.7

    +
      +
    • HANSKEN-15282: Change the way reading data in the python sdk works. More BufferedReader functions are supported, including seeking

    • +
    +
    +
    +

    Release-0.1.6

    +
      +
    • HANSKEN-15294: Make Java SDK available to the Hansken community

    • +
    • HANSKEN-15233: Send initial chunk of data along with the start message (Java only)

    • +
    • HANSKEN-15232: Added RandomAccessData cache mechanism with fixed size of 1 MB to speed up large file reads

    • +
    • HANSKEN-15193: Improve client-side error message when server-side throws an exception

    • +
    • HANSKEN-15237: Added logging before and after processing a trace

    • +
    • HANSKEN-15187: Bugfix: Fixed flits traces with nested map properties were being parsed incorrectly

    • +
    • HANSKEN-15189: Added default log4j logging when no logging is configured

    • +
    +
    +
    +

    Release-0.1.5

    +
      +
    • HANSKEN-15192: Added Superpom for Java extraction plugins

    • +
    • HANSKEN-15186: Fixed matcher $data.mimeType does not work

    • +
    +
    +
    +

    Release-0.1.4

    +
      +
    • HANSKEN-14820: Bugfix: “Received a message from server, but processing of the trace has not been started yet”

    • +
    • HANSKEN-15059: Set up skeleton documentation for Extraction Plugin devs

    • +
    • HANSKEN-15048: Allow ‘workers’ as an optional argument when starting an ExtractionPluginServer

    • +
    +
    +
    +

    Release-0.1.3

    +
      +
    • HANSKEN-14787: Allow extraction plugins to be routed by HEADER by a proxy/loadbalancer

    • +
    +
    +
    +

    Release-0.1.2

    +
      +
    • HANSKEN-14923: Make SDK work on Windows

    • +
    +
    +
    +

    Release-0.1.1

    +
      +
    • HANSKEN-14867: Expanded HQL matcher to support Long & List (intrinsic)properties

    • +
    +
    +
    +

    Release-0.1.0

    +
      +
    • HANSKEN-14879: Allow SDK releases to be published on PyPI

    • +
    • HANSKEN-14738: Shade NFI internal projects into the SDK testframework jar

    • +
    • HANSKEN-14841: Bugfix where RpcStringMap wasn’t being unpacked properly in Python, which was discovered during a flits test.

    • +
    • HANSKEN-14703: Let Python plugins exit gracefully on SIGTERM

    • +
    • HANSKEN-14844: SDK: move serve from test_framework to runtime

    • +
    • HANSKEN-14793: Add ExtractionPluginBuilder.add_data method in python API

    • +
    • HANSKEN-14777: Add an extra check to ignore and log unsupported types during RpcStart gRPC serialization.

    • +
    • HANSKEN-14739: License: Distribute Extraction Plugin SDK under the Apache License 2.0

    • +
    • HANSKEN-14763: Bugfix where some python plugins were not loaded correctly when using serve or test-plugin commands

    • +
    • HANSKEN-14737: Move serve.py from plugin examples to SDK

    • +
    • HANSKEN-14720: Add option to use with when using the trace.open method in python

    • +
    • HANSKEN-14582: Add option to write data using the python api

    • +
    • HANSKEN-14660: Move _test.py files from plugin examples repo to SDK repo

    • +
    • HANSKEN-14618: Add validation for unexpected extra data streams to test framework

    • +
    • HANSKEN-14704: Fix shading of the runtime super pom

    • +
    • HANSKEN-14619: Allow propagation of IOException in plugin new child callback

    • +
    • HANSKEN-14632: Add Java gRPC support for writing raw data streams on a trace

    • +
    • HANSKEN-14591: Split into three modules in the SDK

    • +
    • HANSKEN-14580: Add proto message definitions for raw data stream writing

    • +
    • HANSKEN-14635: Trace format which containes name/id can now be deserialized by testframework

    • +
    • HANSKEN-14131: Added ‘verbose’ logging for test-framework HQL matching

    • +
    • HANSKEN-14130: Updated StandaloneTestRunner to expose more errors & exceptions

    • +
    • HANSKEN-13784: Add meta support to test-framework

    • +
    • HANSKEN-14581: Extend Trace API with raw data writing capabilities

    • +
    • HANSKEN-14547: Deploy Java sources JAR for improved client debugging

    • +
    • HANSKEN-14531: Fix Python release. Python needs only one build step, which is either a snapshot or a release build. The separate Python release step was removed and merged with the first Python build step. Repository paths were corrected for the release version.

    • +
    • HANSKEN-14531: Fix python release - The python release is no longer a separate step in the build pipeline, since there is no actual difference between a snapshot and a release, apart from the version numbering scheme and the test-framework is downloaded from a repository location depending on the release parameter (see comments in Jenkinsfile)

    • +
    • HANSKEN-14161: Add Python test-framework wrapper around Java test-framework and add test-framework.tgz to whl

    • +
    • HANSKEN-14234: Restructure build pipeline to build and release Java first

    • +
    • HANSKEN-13799: Extraction Plugin: support meta extraction

    • +
    • HANSKEN-14286: Create adapter from RandomAccessData to InputStream

    • +
    • HANSKEN-14314: Don’t send child name when sending enrichment message

    • +
    • HANSKEN-14318: Flush cached children before flushing root in case of error with python gRPC server

    • +
    • HANSKEN-13414: Generate shaded jar for runtime that shades Guava, Protobuf, gRPC, and Netty (fix)

    • +
    • HANSKEN-14283: Allow passing a configuration of retry policy for the extraction plugin client

    • +
    • HANSKEN-14234: Restructure build pipeline to build and release Java first

    • +
    • HANSKEN-13414: Generate shaded jar for runtime that shades Guava, Protobuf, gRPC, and Netty

    • +
    • HANSKEN-14234: Restructure build pipeline to build and release Java first

    • +
    • HANSKEN-14135: Improve testing Python plugins in integration step and test reading large chunks

    • +
    • HANSKEN-14134: Fix releasing python plugins

    • +
    • HANSKEN-14128: Validate gRPC message limit for Python server instances

    • +
    • HANSKEN-14122: Fix missing comma in dependencies which broke the release

    • +
    • HANSKEN-14092: The type and total size of the data currently being processed can now be retrieved from an extractioncontext object passed to the process function

    • +
    • HANSKEN-13668: Added support for lists of longs, Hansken maps and LatLong to Java and Python API

    • +
    • HANSKEN-14104: Set Python gRPC limit to 64 MB(including message overhead)

    • +
    • HANSKEN-14079: Add logging to the SDK

    • +
    • HANSKEN-14010: Add support for serializing datetime in python API

    • +
    • HANSKEN-14083: Fix releasing python sdk

    • +
    • HANSKEN-14035: Add static type checks to python project

    • +
    • HANSKEN-14030: Allow test framework to be executed standalone for non-java extraction plugins

    • +
    • HANSKEN-14073: Support gRPC extraction plugins in test framework

    • +
    • HANSKEN-14074: Add support for serialization of Maps.

    • +
    • Hansken-14090: Use new Hansken python-api children call for creating nested children

    • +
    • HANSKEN-13774: Add support for creating children in python API

    • +
    • HANSKEN-13776: Add error handling to the Python based server and send error messages to the client

    • +
    • HANSKEN-14060: Remove mapping-interface from python extraction for API consistency

    • +
    • HANSKEN-13773: Update trace properties in Python API

    • +
    • HANSKEN-14044: Make sure python testing code is linted as well, enforce single quotes

    • +
    • HANSKEN-13772: Expose trace properties in Python API

    • +
    • HANSKEN-13775: Python - added trace.open() functionality to read from data streams

    • +
    • HANSKEN-14031: Split Trace interfaces (hansken.py trace vs external plugin trace)

    • +
    • HANSKEN-14037: Make sure pytest is always used for python tests

    • +
    • HANSKEN-14011: Implement unpack for trace

    • +
    • HANSKEN-14009: Implement pack for trace and trace enrichment

    • +
    • HANSKEN-14008: Move generated hql-lite parsers to different package (conflicts with hql package)

    • +
    • HANSKEN-13777: Added utility to run Python Extraction Plugin implementations with Hansken.py

    • +
    • HANSKEN-13771: Implement Extraction Plugin Info for Python plugins

    • +
    • HANSKEN-13966: Give socketproxy disconnect some time to disconnect (fixes flaky unit test)

    • +
    • HANSKEN-13810: Add extraction plugin python server code

    • +
    • HANSKEN-13676: Added support for ZonedDateTime over gRPC

    • +
    • HANSKEN-13922: Add webpage url to PluginInfo

    • +
    • HANSKEN-13676: Changed the way of creating child Traces to using a consumer.

    • +
    • HANSKEN-13655: Added server/client disconnect tests and implemented initial handling server-side

    • +
    • HANSKEN-13809: Added missing gRPC exception handles of process()

    • +
    • HANSKEN-13801: Made HQL-Lite matchers immutable

    • +
    • HANSKEN-13761: Seperated HQL-Lite type matcher implementation

    • +
    • HANSKEN-13756: Added HQL-Lite datastream matchers

    • +
    • HANSKEN-13800: (Temporarily) remove meta from Extraction Plugin API

    • +
    • HANSKEN-13798: Propagate exception on failure of START serialization

    • +
    • HANSKEN-13706: Create basic test framework implementation

    • +
    • HANSKEN-13769: Make jenkins run tests and a linter for python

    • +
    • HANSKEN-13705: Add support for creating child traces over gRPC

    • +
    • HANSKEN-13713: Make (partial) extraction plugin errors visible for clients

    • +
    • HANSKEN-13709: Send partial result when an external plugin errors out

    • +
    • HANSKEN-13733: Add script to generate gRPC Python files

    • +
    • HANSKEN-13660: Copied the Hql definition from Hansken to enable the HQL-Lite implementation

    • +
    • HANSKEN-13656: Test and handle invalid protocol messages

    • +
    • HANSKEN-13663: Add matcher interface to PluginInfo

    • +
    • HANSKEN-13714: Add IOException to ExtractionPlugin.process() interface

    • +
    • HANSKEN-13658: test(s) for non-grpc connection with a grpc server

    • +
    • HANSKEN-13650: Add basic support for writing trace information over gRPC

    • +
    • HANSKEN-13648: Add basic support for reading trace information over gRPC

    • +
    • HANSKEN-13651: Default implementations for RandomAccessData interface

    • +
    • HANSKEN-13643: Add basic support for reading trace information over gRPC

    • +
    • HANSKEN-13643: Add basic support for reading from trace data over gRPC

    • +
    • HANSKEN-13581: Plugin-info client/server implementations

    • +
    • HANSKEN-13580: Add gRPC server and client to serve an Extraction Plugin as a service

    • +
    • HANSKEN-13579: Created initial gRPC implementation messages

    • +
    • HANSKEN-13577: Be able to create releases of the SDK

    • +
    • HANSKEN-13578: Created plugin API

    • +
    • HANSKEN-13560: Create initial gRPC definitions

    • +
    • HANSKEN-13554: Initial repository

    • +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/contact.html b/0.7.0/contact.html new file mode 100644 index 0000000..3edc1a2 --- /dev/null +++ b/0.7.0/contact.html @@ -0,0 +1,137 @@ + + + + + + + Contact — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Contact

    +

    Please get in touch with us:

    +
      +
    • if you have questions about the SDK,

    • +
    • have found a bug,

    • +
    • have a feature request,

    • +
    • see other opportunity to improve,

    • +
    • want to contribute,

    • +
    • +
    +

    Chat with us on Discord. You can find members of the extraction plugin SDK development team +in the Hansken Community server, in the extraction-plugins channel. This is a Hansken community-private server. If you +don’t have access to the Hansken Community server, please contact your Hansken business owner. He or she can invite you +to the Hansken Community server. If you don’t know who to contact, feel free to fill in +the contact form for further questions/contact.

    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/concepts.html b/0.7.0/dev/concepts.html new file mode 100644 index 0000000..50e7a18 --- /dev/null +++ b/0.7.0/dev/concepts.html @@ -0,0 +1,152 @@ + + + + + + + General concepts — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/0.7.0/dev/concepts/all_in_one_debugging.html b/0.7.0/dev/concepts/all_in_one_debugging.html new file mode 100644 index 0000000..d3df4a3 --- /dev/null +++ b/0.7.0/dev/concepts/all_in_one_debugging.html @@ -0,0 +1,198 @@ + + + + + + + Debugging locally with Hansken All in One (AIO) — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Debugging locally with Hansken All in One (AIO)

    +
    +

    Note

    +

    this feature is available from Hansken 46.4.0

    +
    +
    +

    Warning

    +

    DeferredExtractionPlugin are currently NOT supported

    +
    +

    It is also possible to debug an Extraction Plugin with a locally running Hansken AIO. This requires a few steps:

    +
      +
    1. Start your plugin in your IDE (default port 8999)

      +1_debug_start_plugin_in_ide.png +
    2. +
    3. Set some breakpoint(s)

      +2_debug_set_breakpoints.png +
    4. +
    5. Prepare an extraction in the AIO with advanced options

      +3_debug_prepare_extraction.png +
    6. +
    7. Enable the DebugExtractionPluginTool for this extraction

      +4_debug_enable_debug_tool.png +
    8. +
    9. Start extraction, and happy debugging!

    10. +
    +
    +

    Tips/notes:

    +
      +
    • You can only debug 1 plugin at a time

      +
        +
      • +

        Note

        +

        If you are debugging MyPlugin, and it is also visible in the tools list, then you need to disable it, and only enable the DebugExtractionPluginTool

        +
        +
      • +
      +
    • +
    • Test your plugin with a small image. Otherwise, it might take long before you reach your breakpoint.

    • +
    • Be careful when using this debugger with APPEND extractions:

      +
        +
      • Similar to other tools/plugins, the DebugExtractionPluginTool will only run once per trace

      • +
      • So if you need to re-run your debug session, then we advise you to re-extract (INDEX) your project instead

      • +
      +
    • +
    • Hansken runs multiple instances of every Tool, so the same breakpoint can be hit multiple times concurrently by +different instances

      +
        +
      • +

        Note

        +

        UNSUPPORTED: it is currently not supported to limit the number of plugin threads/workers

        +
        +
      • +
      +
    • +
    • Only restart your plugin before starting an extraction. Restarting your plugin during an extraction can +produce +undefined behaviour.

    • +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/concepts/anatomy_of_a_plugin.html b/0.7.0/dev/concepts/anatomy_of_a_plugin.html new file mode 100644 index 0000000..d2c966f --- /dev/null +++ b/0.7.0/dev/concepts/anatomy_of_a_plugin.html @@ -0,0 +1,221 @@ + + + + + + + Anatomy of a plugin — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Anatomy of a plugin

    +

    This page describes the general anatomy of a plugin, and its (simplified) execution in Hansken.

    +
    +

    The plugin itself

    +

    Each plugin must implement two methods:

    +
      +
    • pluginInfo(): This method returns information about the plugin.

    • +
    • process(): This method performs the extraction task of the plugin.

    • +
    +

    Lets dive a bit deeper into these methods in the next sections!

    +
    +

    The method pluginInfo()

    +

    The pluginInfo() method returns a PluginInfo object. Hansken needs this object to be able to know the capabilities +of the plugin, and to show the plugin in the list of tools. The most important fields that must be set on PluginInfo +are the following:

    +
    +
    id | The identifier of the plugin. This will be used as a unique name for the plugin Hansken. |
    +
    description | A description of the plugin that is shown in Hansken. |
    +
    author | The author of the plugin that is shown in Hansken. |
    +
    license | The type of license of the plugin that is shown in Hansken. |
    +
    matcher | This matcher is used by Hansken to determine which Traces are sent to the Plugin during extraction. |
    +
    +
    +
    +

    The method process()

    +

    During extraction, Hansken calls the process() method for every matching trace. The matcher attribute of +the PluginInfo is very important as it determines which traces will be sent to the process method.

    +

    Although the plugin developer is free to program whatever seems useful, the following tasks are typically performed +within the process() method:

    +
      +
    • Creating child-traces

    • +
    • Reading trace properties

    • +
    • Adding trace properties

    • +
    • Reading the data that the Trace represents

    • +
    • Writing data on a Trace

    • +
    +

    Depending on the type of plugin that is implemented, different functionality is available in the process() method. +See Plugin Types for more details.

    +
    +
    +
    +

    The execution in Hansken

    +

    This describes the process of running a plugin from the perspective of Hansken. The perspective of the user is described +in Hansken Extraction Plugins.

    +
    +

    Plugin discovery

    +

    Hansken manages a list of tools that can be used in extractions. The available plugins must be added to this list so +that the user can select them. To accomplish this, Hansken scans the Docker registry for docker images that are plugins. +Each image is started up, and a call is done to its pluginInfo() method. If the call resulted in a valid PluginInfo +object, the Extraction Plugin is added to the list of tools visible to users. After the PluginInfo is retrieved, the +docker image is shutdown again.

    +
    +
    +

    Starting an extraction

    +

    Hansken checks if any plugins are selected by the user that started the extraction. For each selected plugin, at least +one docker image will be started. See Kubernetes autoscaling for more details on expanding +the number of instances for each plugin.

    +
    +
    +

    Extracting

    +

    During an extraction, Hansken will iteratively loop through all selected tools, including Extraction Plugins. For each +trace that matches on a tool, Hansken will call its process method. For Extraction Plugins, this means that +the process method is called via the gRPC protocol. The trace to be processed is sent over gRPC to the plugin, and any +other communication between Hansken and the Extraction Plugin (like created properties and child traces, search requests +and written data) are done using gRPC.

    +
    +
    +

    Finishing an extraction

    +

    At the end of an extraction, Hansken will stop all associated plugins.

    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/concepts/data_transformations.html b/0.7.0/dev/concepts/data_transformations.html new file mode 100644 index 0000000..855187c --- /dev/null +++ b/0.7.0/dev/concepts/data_transformations.html @@ -0,0 +1,161 @@ + + + + + + + Data Transformations — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Data Transformations

    +

    Extraction Plugins can create new data-streams on a Trace through data transformations. Data +transformations describe how data can be obtained from a source. Data transformations are preferred over storing blobs +because they take less space. This is because they only describe the data instead of specifying the actual data.

    +

    The following figure shows how Hansken visualizes data transformations:

    +datatransformations.png +

    Note that transformations can be applied on transformations. The SDK only supports range transformations at the moment, +while this image also shows some transformations that are currently available in Hansken but not in the SDK.

    +

    An example case is an extraction plugin that processes an archive file. The plugin creates a child trace per entry in +the archive file. Each child trace will have a data stream that is a transformation that marks the start and length of +the entry in the original archive data. By just describing the data instead of specifying the actual data, a lot of +space is saved.

    +

    Although Hansken supports various transformations, the Extraction Plugins SDK for now only supports ranged data +transformations. Ranged data transformations define data as a list of ranges, each range with an offset and length in a +bytearray.

    +
    +

    See also

    + +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/concepts/extraction_plugins.html b/0.7.0/dev/concepts/extraction_plugins.html new file mode 100644 index 0000000..9095736 --- /dev/null +++ b/0.7.0/dev/concepts/extraction_plugins.html @@ -0,0 +1,201 @@ + + + + + + + Hansken Extraction Plugins — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Hansken Extraction Plugins

    +

    Hansken Extraction Plugins enable Hansken users to add their own extraction tools to Hansken.

    +

    To use an Extraction Plugin in Hansken, the following steps have to be done:

    +
      +
    1. Build the plugin

    2. +
    3. Upload the plugin to Hansken

    4. +
    5. Refresh the Hansken tools list

    6. +
    7. Start an extraction with the plugin enabled

    8. +
    +
    +

    Building a plugin

    +

    Hansken Extraction Plugins can be built in Java or Python by implementing an interface. Which interface you choose +depends on the type of plugin you choose to make, see Plugin Types. For more information on coding +your own plugin, see the Extraction Plugin Examples.

    +

    The plugin can then be tested using the Test Framework. This way you make sure everything works as +expected before taking further steps.

    +
    +
    +

    Package the plugin

    +

    To upload a plugin into Hansken, a docker image for this plugin must be uploaded to the docker registry. First, the +plugin container image must be packaged. +A plugin is packaged into an OCI image (also known as Docker image).

    + +
    +
    +

    Upload the plugin to Hansken

    +

    Hansken finds the plugins by scanning a docker registry. It will try to load all docker images with a certain prefix as +Extraction Plugins. The settings for this are defined in Hansken properties:

    +
      +
    • registry.extraction.plugins.registry.uri defines the registry

    • +
    • registry.extraction.plugins.registry.prefix defines the prefix plugins must have

    • +
    +

    When the image is packaged locally, it needs to be pushed (uploaded) to the docker registry. These commands provide an +outline of how to do this:

    +
      +
    • docker login <docker-registry> (make sure you are logged in to the registry)

    • +
    • docker push <docker-registry><prefix><pluginname> (push the plugin docker image to the registry)

    • +
    +
    +

    Note

    +

    For more information about uploading plugins and running them in Hansken, see the ‘Using extraction plugins in +Hansken’ chapter of the Hansken User Guide.

    +
    +
    +
    +

    Refresh the Hansken tools list

    +

    Hansken checks which plugins are available at startup. The list of available plugins can also be refreshed by calling +the following endpoint: <hansken-domain>/gatekeeper/tools?refresh=true

    +

    This can be invoked via an internet browser.

    +
    +
    +

    Start an extraction with the plugin enabled

    +

    If everything went well, the list of available tools in Hansken should now feature your plugin. To run the plugin in an +extraction, be sure to select its checkbox in the extraction tools dialog.

    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/concepts/hql_lite.html b/0.7.0/dev/concepts/hql_lite.html new file mode 100644 index 0000000..b91f320 --- /dev/null +++ b/0.7.0/dev/concepts/hql_lite.html @@ -0,0 +1,579 @@ + + + + + + + HQL-Lite — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    HQL-Lite

    +
    +

    Overview

    +

    HQL-Lite is a query language derived from Hanskens full HQL human. HQL stands for Hansken Query Language and can be +used to search or match traces. Since not all elements of full HQL can be used in the context of an extraction, +extraction plugins use HQL-Lite, a lightweight version of HQL. This document describes the usage of HQL-Lite in the +context of extraction plugins.

    +
    +
    +

    How does Hansken work?

    +
      +
    • Let’s say we have a Hansken image hansken_image1 with 10 pdf files, and 5 jpegs.

    • +
    • And our Hansken contains 2 tools:

      +
        +
      • PdfPlugin

      • +
      • JpegTool

      • +
      +
    • +
    +
    +

    Note

    +

    All plugins are Hansken tools, but not all Hansken tools are plugins. Some tools are included in Hansken core.

    +
    +

    Let’s look at a (simplified) pseudocode example of the inner workings of Hansken:

    +
    for each trace in new_traces {
    +    for each datastream in trace {
    +        for each tool in hansken_tools {
    +            if tool.can_this_tool_process_the_provided_trace(trace, datastream) {
    +                tool.process_the_trace(trace, datastream)
    +            }
    +        }
    +    }
    +}
    +
    +
    +

    So in this example we know the following:

    +
      +
    • new_traces has

      +
        +
      • 10 pdf files

      • +
      • 5 jpeg files

      • +
      +
    • +
    • hansken_tools contains:

      +
        +
      • PdfPlugin

      • +
      • JpegTool

      • +
      +
    • +
    +

    So the question here is, how do we prevent that traces are not processed by incompatible tools?

    +

    The answer is the tool.can_this_tool_process_the_provided_trace() part of the pseudocode.

    +
    +

    What does can_this_tool_process_the_provided_trace() do?

    +

    Hansken actually contains many more tools/plugins than these 2, and instead of 15 files/traces, we usually deal with +millions.

    +
    +

    Note

    +

    If each trace has 1 extra second of overhead, 1 million traces would take 11.5 days of extra CPU time

    +
    +
    +

    Matchers to the rescue

    +

    To reduce the unnecessary overhead of processing all traces (even the ones the tool cannot actually process), Hansken +implements the concept of a matcher for each tool. This matcher basically checks the trace for “matching +conditions”, that would allow the tool to process it.

    +

    Sometimes these matching conditions can be as simple as a specific filename or extension, but are often more +elaborate in the sense that they check multiple factors that require some intimate knowledge of Hansken.

    +
    +
    +
    +
    +

    What is HQL-Lite?

    +

    HQL-Lite is a language based on HQL (Hansken Query Language) that allows plugin developers to write matchers for +Hansken Extraction Plugins. It could be said that HQL-Lite contains a subset of HQL features, plus some HQL-Lite unique +features that are only interesting for matchers.

    +
    +

    Note

    +

    Please note that even though the HQL-Lite query is part of the plugin, it is compiled and stored in Hansken during +startup to achieve performance.

    +
    +
    +

    Why not just use HQL for plugins?

    +

    HQL was designed to search for traces stored in the Elasticsearch database. As such, some of its features are tightly +coupled to the Elasticsearch implementation, making it difficult to re-implement them for plugins.

    +

    Also, even though HQL is more complex than the requirements for matching in plugins, a couple of minor features that +are absolutely necessary for matching are not implemented in HQL, as they don’t make much sense from a search point of +view. This is because HQL was designed to be used with finished extractions with all the traces stored in the +database, while HQL-Lite was designed for active extractions.

    +
    +
    +

    HQL-Lite syntax

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Matcher

    Syntax

    remarks

    All

    ""

    an empty string translates to match for all traces

    And

    foo:1 AND bar:2

    the case-sensitive AND operator behaves like a logical AND of 2 conditions

    Not

    NOT foo or -foo

    the case-sensitive NOT or - negates the expression that follows

    Range

    foo>1 or 1<=foo<10

    a numbered-range check with a min or/and max range(s)

    Or

    foo:1 OR bar:2

    the case-sensitive OR operator behaves like a logical OR of 2 conditions

    Data

    $data.foo:1

    see $data section below

    DataType

    $data.type:raw

    this query matches against the type of the current datastream

    Types

    type:email

    this query checks if the trace contains a certain trace type as defined in the Hansken trace model

    +

    There are also a couple of general guidelines that apply to all matchers:

    +
      +
    • Equals/not equals:

      +
        +
      • : or = : The most basic of left equals right statements. note that = is also valid.

      • +
      • != : The opposite of equals, not equals. Note that !: is NOT supported.

      • +
      +
    • +
    • Wildcards:

      +
        +
      • ? : Match against any single character. E.g. foo:r?w will match against raw, row but not against rowing.

      • +
      • * : Match against any chars. E.g. foo:r* will match against r, ra, raw, raaaaaw but not against aw.

      • +
      +
    • +
    • Exact match: By surrounding a value with quotes, we tell the parser that it is a single value. This is especially +helpful for values that might contain separators. E.g. foo:'hello hql-lite'.

    • +
    • CSV: Currently only the type query supports multiple values to check against. E.g. type:email,chatMessage will only +return true if both types exist for this trace.

    • +
    • () grouping: You can group statements by putting brackets around them. E.g. foo:1 AND (bar:2 OR bla:3) which +translates to foo:1 plus one of the statements in the brackets.

    • +
    • Escaping \"\.\t\r\n:=><!()~/,[]{}: Some characters are used internally by HQL-Lite, and need to be escaped if they +are used in the value side of the key-value pair. These values can be escaped by adding prepending \\ to the +character(s). Example: foo:foo bar should be foo:foo\\ bar, foo.bar:foo:bar should be foo.bar:foo\\:bar +…etc.

      +
        +
      • The only exceptions to this rule are unix paths:

        +
          +
        • Acceptable paths:

          +
            +
          • foo:/

          • +
          • foo:bar/baz

          • +
          • foo:/bar/baz

          • +
          • foo:'/bar/baz/he llo'

          • +
          • foo:*bar/baz*

          • +
          +
        • +
        • Unacceptable paths:

          +
            +
          • foo:/bar/ -> this is the regex matcher, which is unsupported in HQL-lite

          • +
          • foo:c:\ -> should be foo:c\:\\, both the colon and the slash need to be escaped

          • +
          • foo:'c:\' -> should be foo:'c:\\', the slash still needs to be escaped

            +
              +
            • +

              Note

              +

              the backslash is the universal escape character, so it always needs to be escaped.

              +
              +
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    +
    +

    $data matchers

    +

    In Hansken, a trace can have multiple datastreams. The exact content of said datastreams is +discussed elsewhere, but the basic idea is that a trace can have multiple representations. For example, a trace might +have a raw datastream, but after we identify that the raw bytes contain a text file, we might add a separate +datastream text.

    +
    +

    Note

    +

    The process() method of each plugin is called for each datastream of each trace. This is explained +in How does Hansken work? . Subsequently, you might have the same property for a +different datastream. For example: you might have a data.raw.size and a data.text.size property. The reason you +might have the same property multiple times, is because it could have a different meaning.

    +
    +

    For example:

    +
      +
    • data.raw.size: is the size in bytes

    • +
    • data.text.size: is the number of bytes in the text representation of the raw stream

    • +
    +

    If we want to check if either of these properties is not empty by using a $data matcher, we do:

    +
    $data.size>0
    +
    +
    +
    +
    When is it useful to use a $data matcher?
    +

    For example, there is a simple plugin called LetterCountPlugin, that counts the letters in text based datastreams.

    +

    So to match on these text based datastreams, we have 2 choices:

    +
      +
    • List all the possibilities

      +
        +
      • Which is too tedious, and not very flexible when new types are supported

      • +
      +
    • +
    • Match on a common property

      +
        +
      • More compact, but sometimes difficult to find a common property

      • +
      +
    • +
    +

    In this case we might match on mimeType, which we know is text/plain or text/x-log for 2 of types we want to match:

    +
    $data.mimeType=text\\/*
    +
    +
    +

    This will match the following:

    +
      +
    • data.text.mimeType=text\\/plain

    • +
    • data.text.mimeType=text\\/not\\ plain

    • +
    • data.pdf.mimeType=text\\/encoded

    • +
    • data.foo.mimeType=text\\/bar

    • +
    +

    But will not match any of the following:

    +
      +
    • data.text.mimeType=txt

    • +
    • data.text.mimeType=pdf

    • +
    • data.text.mime=text\\/plain

    • +
    • data.foo.bar=text\\/plain

    • +
    +
    +
    +
    +
    +
    +

    How to write a matcher?

    +

    The functional requirements for writing a matcher can be summarized in the following:

    +
      +
    1. What does my plugin expect as input?

    2. +
    3. How can I describe that input with the information Hansken provides?

    4. +
    +
    +

    PdfPlugin example

    +

    Let’s say we just finished writing a PdfPlugin. This is a simple plugin that checks if pdf files contain the +word the.

    +

    So let’s go over our checklist:

    +
    +

    What does my plugin expect as input?

    +

    PDF files.

    +
    +
    +

    How can I describe that input with the information Hansken provides?

    +

    Hansken consumes and produces Traces. To that effect, we can only match on trace properties that are +available in Hansken.

    +
    +
    Match on extension
    +

    The easiest way would be to only allow traces with the .pdf extension. Looking at the Hansken trace model (or a +Hansken extraction), we can see that there’s a property file +which contains a property extension.

    +

    So what would that look like in HQL-lite? Something like

    +
    file.extension=pdf
    +
    +
    +
    +

    Warning

    +

    This of course only works if the file has the correct extension (note that matchers are case-sensitive).

    +
    +

    So what do we do, if we also want to match pdf files that are (un)intentionally misnamed?

    +
    +
    +
    Match on mime-type
    +

    Looking at Wikipedia, we see that pdf has a couple of mime-types. In return looking at our extraction and the +trace-model, we see both at data.raw.mimeType, with a further explanation in the Hansken trace model that +the raw portion of the property is the data type of the datastream.

    +

    If we don’t know which datastream has the mimeType property beforehand, we could use the broad-scoped $data. matcher +to look at every datastream.

    +

    So our matcher becomes:

    +
    file.extension=pdf OR
    +(
    +  $data.mimeType=application\\/pdf OR
    +  $data.mimeType=application\\/x-pdf
    +)
    +
    +
    +
    +
    +
    Match on data size
    +

    Some pdf files can be huge, meaning that parsing them will need a lot of resources. Could we add a data size check to +the matcher? According to the Hansken trace model data has a property size (similar to mimeType) that we +could use for this.

    +
    +

    Note

    +

    This is also a good way to check if a file is empty or not.

    +
    +

    Let’s say our cutoff limit is 1 MB, meaning our matcher becomes:

    +
    0 < $data.size < 1000000 AND
    +(
    +  file.extension=pdf OR
    +  (
    +    $data.mimeType=application\\/pdf OR
    +    $data.mimeType=application\\/x-pdf
    +  )
    +)
    +
    +
    +
    +
    +
    Match if ‘property is set’
    +

    It is not uncommon to have some overlap between tools/plugins. For example:

    +
      +
    • PdfPlugin: a plugin that only supports pdf documents

    • +
    • DocumentPlugin: this plugin supports a lot of document types, including pdf.

    • +
    +

    So how would we prevent our plugin from processing a trace that has already been processed by the DocumentPlugin?

    +

    The easiest solution would be to check if a certain property has already been set. Meaning, that if both plugins set +the foo.bar property, we check if said property has already been set.

    +

    So we only process the trace if foo.bar is empty, meaning our matcher becomes:

    +
    foo.bar!=* AND
    +0 < $data.size < 1000000 AND
    +(
    +  file.extension=pdf OR
    +  (
    +    $data.mimeType=application\\/pdf OR
    +    $data.mimeType=application\\/x-pdf
    +  )
    +)
    +
    +
    +
    +
    +
    Match on excluding a certain path
    +

    It is also not uncommon to exclude certain paths from your plugin. These paths might contain invalid or encrypted files, +for example.

    +

    So let’s say we want to exclude all files under in the /tmp/virus path. How do we go about it?

    +

    Again, we check our extraction/Hansken trace model, and we see that file.path looks promising.

    +

    So excluding /tmp/virus would look something like:

    +
    -file.path=/tmp/virus* AND
    +foo.bar!=* AND
    +0 < $data.size < 1000000 AND
    +(
    +  file.extension=pdf OR
    +  (
    +    $data.mimeType=application\\/pdf OR
    +    $data.mimeType=application\\/x-pdf
    +  )
    +)
    +
    +
    +
    +
    +
    Match on specific datastream type, an anti-pattern
    +
    +

    Warning

    +

    Matching on specific datastream types is an anti-pattern! It is not recommended to match on a datastream +type. Instead one should match on other datastream properties, such as fileType, mimeType or mimeClass. +The reason for this is explained in the paragraph below.

    +
    +

    Using a matcher that is too loose or too tight can yield unexpected results. Usually one will match on properties +of a datastream like fileType, mimeType or mimeClass as these are reliable properties that have been added by +Hansken tools. Matching on a specific datastream says nothing about the type of file. For example a PDF file may be +available in a raw as well as in a decrypted datastream. By matching on the datastream type one may exclude traces +that were not intended to be excluded. +Contrarily, note that matching on a datastream type may include more traces than you expected as well. For example, +someone may think “Plugin A puts data on the plain datastream, so I’ll match on the plain datastream with Plugin B”, +forgetting that plain may be used by other tools as well. In other words, there may be traces with that datastream +type that you did not know of, potentially crashing your plugin. See Data streams for more information.

    +

    Now that you know why it is an anti-pattern, lets explain how it would be done (for those edge cases where it’s needed): +Lets say we want our PdfPlugin to ONLY process raw datastreams. +The best way to do this would be to match +on $data.type:raw. Note that $data.type matches against the type of the current datastream, so in this case it +matches only when the current datastream is of type raw.

    +

    An incorrect way to do it would be to replace $data. matcher(s) with data.raw.. This means the matcher +will match whenever a trace has this datastream type, even if the current datastream type is different. +Remember that the process method of an extraction plugin is always called once for each datastream on each trace. +For example, lets say a trace has two datastreams, raw and text. The matcher would match for both the datastreams +because the trace has a raw datastream (even though the current datastream type may be text). This results in the +process method being called twice (for raw and for text), which may lead to other bugs if the developer doesn’t +know this. For example, the second time the plugin may be trying to overwrite data on a trace which is prohibited.

    +

    So, using $data.type, our matcher would look like:

    +
    $data.type:raw AND
    +-file.path=/tmp/virus* AND
    +foo.bar!=* AND
    +0 < $data.size < 1000000 AND
    +(
    +  file.extension=pdf OR
    +  (
    +    $data.mimeType=application\\/pdf OR
    +    $data.mimeType=application\\/x-pdf
    +  )
    +)
    +
    +
    +
    +
    +
    +
    +
    +

    How precise should a matcher be?

    +

    In practice, only you as the plugin dev can answer this question.

    +

    Know that from the point of view of Hansken, we only care that the plugin:

    +
      +
    • Should not crash: If a matcher does not compile, then your plugin will not be available in Hansken. Tip: be sure +to test your plugin with the test framework.

    • +
    • Should not be slow: Matching is designed to be extremely fast, but of course, if you make it too complex it can +take longer than we want. In the example above, we calculated that 1 second extra for 1 million traces is 11 days of +extra CPU time. Unlike processing, matching is done for every trace, in every extraction iteration, so be careful!

    • +
    • Should match on the bare minimum: Don’t go too far by matching 50 different criteria before allowing a trace to be +processed. Note that a lot of (if not all) of these criteria depend on properties set by other tools, and you don’t +really have any control on how these tools work.

    • +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/concepts/isolation.html b/0.7.0/dev/concepts/isolation.html new file mode 100644 index 0000000..de9ba05 --- /dev/null +++ b/0.7.0/dev/concepts/isolation.html @@ -0,0 +1,168 @@ + + + + + + + Plugin isolation — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Plugin isolation

    +

    Extraction plugins allows arbitrary code to be executed during a Hansken extraction. This code is executed inside the +Hansken cluster. Extraction plugins are subjected +to Hanskens design principles +such as security, privacy and transparency. To ensure that plugins are compliant to these principles, each plugin will +be executed in isolation. This page describes the isolation measures that are in place.

    +
    +

    User isolation

    +

    The following user restrictions are implied on the plugin:

    +
      +
    • The plugin can not run as root.

    • +
    • Instead, the plugin will run as user 1000

    • +
    • and with group 2000

    • +
    • and with fsgroup 3000

    • +
    +
    +
    +

    System calls

    +

    Plugins are only allowed to call a limited set of (Linux) system calls. This ensures that a plugin can be executed in a +secure manner within the Hansken platform.

    +

    Hansken uses Kubernetes to run extraction plugins. The Kubernetes RuntimeDefault secure computing mode (seccomp) is +enabled to provide a sane default of available system calls.

    +
    +
    +

    Network isolation

    +

    Plugins are not allowed to communicate over the network.

    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/concepts/kubernetes_autoscaling.html b/0.7.0/dev/concepts/kubernetes_autoscaling.html new file mode 100644 index 0000000..d043ebd --- /dev/null +++ b/0.7.0/dev/concepts/kubernetes_autoscaling.html @@ -0,0 +1,166 @@ + + + + + + + Kubernetes, Autoscaling, Resourcemanagement — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Kubernetes, Autoscaling, Resourcemanagement

    +

    Extraction Plugins can be run in a Kubernetes cluster. This can be the same cluster Hansken run on, or another external +cluster. For each plugin, a pod is created by Hansken. Each plugin will have 12 threads by default to process traces +separately within one pod.

    +
    +

    Autoscaling

    +

    Hansken will create a Horizontal Pod Autoscaler (HPA) for each pod. HPA’s manage the number of replica’s of a pod +based on metrics. The Extraction Plugins SDK provides two metrics to be set in the PluginInfo:

    +
      +
    • Observed CPU utilization

    • +
    • Observed memory usage

    • +
    +

    For more info on how to set these metrics follow these links:

    + +

    If a pod reaches the CPU or memory usage provided with the PluginInfo, the HPA will increase the number of replicas for +that plugin. Scaling down is done automatically. The maximum number of replicas per pod is specified within Hansken +properties and can be adapted by an operator (needs restart).

    +
    +
    +

    Finding the right settings

    +

    This depends on the kubernetes cluster settings, the nodes it runs on, and of course the plugin itself. Monitor the +number of replica’s and resource metrics while an extraction is running and adapt accordingly.

    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/concepts/plugin_naming_convention.html b/0.7.0/dev/concepts/plugin_naming_convention.html new file mode 100644 index 0000000..7559445 --- /dev/null +++ b/0.7.0/dev/concepts/plugin_naming_convention.html @@ -0,0 +1,237 @@ + + + + + + + Plugin naming convention — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Plugin naming convention

    +
    +

    Plugin identifier

    +

    Each extraction plugin has a unique identifier. The identifier consists of three fields. These three fields combined +form the plugin name.

    +

    The three fields of a plugin identifier are: domain, category, and name. The fields are described in more detail +below.

    +

    domain +The domain name of the organisation where the plugin is created. If an organisation has multiple domain names, the +shortest name is preferred over the longer domain names. Examples: nfi.nl, politie.nl, fiod.nl, hansken.org.

    +

    category +A type of action that the plugin performs. The category is a free text field, but the following table gives some +recommendations.

    + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Category

    Description

    extract

    The plugin parses a clear data structure

    carve

    The plugin parses data fragments to reassemble traces in the absence of filesystem metadata

    classify

    The plugin categorizes a plugin based on its content, e.g. detecting money on traces of type picture

    digest

    The plugin digests data to compute a hash

    ocr

    The plugin applies ocr (optical character recognition) to read text on pictures or scanned documents

    match

    The plugin matches a trace against a database, and reports whether there was hit or miss, e.g. matching a trace to a well known files database

    +

    name +The name of the plugin, or in the classic sense, a description detailing what the plugin processes. Note that the name +can contain (forward) slashes.

    +
    +
    +

    Examples

    +

    The following table shows a list of plugin identifiers. The last column of the table shows the derived full plugin name. +The derived full plugin name will be shown in Hansken.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Domain

    Category

    Name

    Derived plugin name

    Explanation

    hansken.org

    extract

    archive

    hansken.org/extract/archive

    A plugin created by the Hansken development team that extracts traces from an arbitrary archive format

    nfi.nl

    extract

    archive/zip

    nfi.nl/extract/archive/zip

    A plugin created by an NFI team that extracts traces from a specific archive format: zip

    politie.nl

    extract

    archive/zip

    politie.nl/extract/archive/zip

    The same as the previous example, but now the plugin is created by a different organisation: politie.nl

    hansken.org

    carve

    archive/zip

    hansken.org/carve/archive/zip

    A plugin that carves data to detect a specific archive format: zip

    hansken.org

    digest

    sha256

    hansken.org/digest/sha256

    A plugin that digests data to compute a sha256 hash

    hansken.org

    ocr

    tesseract

    hansken.org/ocr/tesseract

    A plugin that performs ocr using tesseract

    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/concepts/plugin_types.html b/0.7.0/dev/concepts/plugin_types.html new file mode 100644 index 0000000..1c44ad4 --- /dev/null +++ b/0.7.0/dev/concepts/plugin_types.html @@ -0,0 +1,199 @@ + + + + + + + Extraction plugin types — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Extraction plugin types

    +

    Currently three types of Hansken Extraction Plugins are supported:

    +
      +
    • Standard Extraction Plugins

    • +
    • Meta Extraction Plugins

    • +
    • Deferred Extraction Plugins

    • +
    +
    +

    Standard Extraction Plugins

    +

    Standard Extraction Plugins provide enough functionality for most cases. This includes:

    +
      +
    • Processing traces

    • +
    • Creating child-traces

    • +
    • Reading trace properties

    • +
    • Adding trace properties

    • +
    • Reading data

    • +
    • Writing data

    • +
    • Writing data transformations

    • +
    +
    +
    +

    Meta Extraction Plugins

    +

    Meta Extraction Plugins can only process and produce trace properties without the need (or possibility) for processing +actual trace data. This includes:

    +
      +
    • Processing traces

    • +
    • Creating child-traces

    • +
    • Reading trace properties

    • +
    • Adding trace properties

    • +
    +
    +
    +

    Deferred Extraction Plugins

    +

    These plugins have two additional features that are not included with Standard Extraction Plugins:

    +
      +
    • a developer can choose to defer their execution

    • +
    • information about other traces can be obtained while processing the current extraction trace. Code examples can be +found here: Java, Python.

    • +
    +

    Deferring execution +A single Hansken image extraction consists of multiple iterations. Within every iteration, every Hansken tool and +plugin is executed on matching traces, which produces new traces or modifies existing traces. If a tool can be executed +another time because of these additions or modifications, another iteration is started.

    +

    A regular plugin is executed in the same iteration a trace is matched. Deferred plugins are executed in a different +iteration; they are always deferred for at least one iteration. This is very useful when searching for traces, because +you are certain the deferred plugin is executed after all other tools performed their modifications.

    +

    Sometimes, executing your plugin in the next iteration is not enough; it needs to be executed in a different iteration. +This is why the SDK allows setting a deferredIterations parameter in the plugin info. After the plugin matches with a +trace, it will be executed after deferredIterations. The execution can currently be deferred by a maximum of 20 +iterations and the default is 1.

    +

    Searching for traces +This type of plugin can perform a search to look for saved traces in the current project. This search is performed +using a provided HQL query. A maximum of 50 traces is returned for a given search request.

    +
    +

    Warning

    +

    Please note that HQL-Lite specific syntax such as the Data or DataType matchers is NOT supported.

    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/concepts/test_framework.html b/0.7.0/dev/concepts/test_framework.html new file mode 100644 index 0000000..704158a --- /dev/null +++ b/0.7.0/dev/concepts/test_framework.html @@ -0,0 +1,432 @@ + + + + + + + Test framework — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Test framework

    +

    The SDK provides the FLITS Test Framework for integration testing. This allows us to test/validate the plugin input +and output without having a running Hansken instance.

    +

    To use the test framework, three components are required:

    +
      +
    1. A running server instance of an extraction plugin. See section How to test your plugin.

    2. +
    3. Input test data

    4. +
    5. Results (expected output)

    6. +
    +
    +

    Creating test data

    +

    The test data is independent of which programming language is used for the plugin (Java or Python). This section +describes the setup of the test data, while the sections thereafter will link to the language specific documentation.

    +
    +

    Basic test data directory structure

    +

    Example test data directory structure with an inputs and results directory:

    +
    tests/
    +├── inputs
    +│   ├── example1.raw
    +│   ├── example1.text
    +│   ├── example1.trace
    +│   ├── example2.raw
    +│   └── example2.trace
    +└── results
    +    ├── example1.raw.PluginName.trace
    +    ├── example1.text.PluginName.trace
    +    └── example2.raw.PluginName.trace
    +
    +
    +

    The inputs folder contains all traces that will be processed during the test. These ‘input traces’ are defined in +files with the ‘.trace’ extension, using JSON. This JSON structure is explained in section +Trace format. Each trace may have various data-streams. The data for each trace +is put into separate files for each data-stream. The data-stream files need to have the same name as their corresponding +trace file but differ in extension. They can have any extension, for example ‘raw’, ‘text’ or ‘jpeg’. Note that one +input trace will always have one ‘.trace’ file, and can have none, one or many data files. Also note that if the +plugin doesn’t match on any of the input files and there are no result files yet, the test will succeed.

    +
    +

    Note

    +

    The test-framework uses the extension of the input test file(s) _(other than __.trace__)_ as type of the +current data-stream.

    +
    +

    The expected results (which are also traces) are stored in a separate results folder next to the inputs folder. The +file names in the results folder correspond to the file names in the inputs folder. Note that the name of the plugin +is added between the file basename and the file extension. This can be useful if one maintains a single test input and +output test datasets for multiple extraction plugins.

    +
    +

    Note

    +

    It is possible to let the test framework regenerate the results files automatically. See the +Java and Python sections on testing on how to do this. If no files are +being generated, check if the plugin matcher is actually matching the input files.

    +
    +

    The test runner will invoke the extraction plugin for each input trace. The test runner collects the plugin output and +compares it against the trace defined in the results folder. If there is a mismatch, the test runner will fail with an +exit code 1. If all tests pass the test runner will finish with exit code 0.

    +

    Given the files in the example above, the test runner will invoke the extraction plugin three times:

    + + + + + + + + + + + + + + + + + +

    Input

    Result

    example1.trace with data stream example1.raw

    example1.raw.PluginName.trace

    example1.trace with data stream example1.text

    example1.text.PluginName.trace

    example2.trace with data stream example2.text

    example2.raw.PluginName.trace

    +
    +
    +

    Test data structure for deferred extraction plugins

    +

    Deferred extration plugins have the unique ability to search traces with a query. +The input test data should be extended to contain the results of searches done by deferred extraction plugins. These +search traces are stored in separate folders that follow the naming format ‘{deferred trace name}/searchtraces/’. Below +is an example test data directory structure for a deferred extraction plugin that searches for +a deferredExampleSearch.trace:

    +
    tests/
    +├── inputs
    +│   ├── deferredExample.trace
    +│   ├── deferredExample.raw
    +│   ├── deferredExample/
    +│   │   ├── searchtraces/
    +│   │   │   ├── deferredExampleSearch.trace
    +└── results
    +    └── deferredExample.raw.DeferredPluginName.trace
    +
    +
    +
    +

    Warning

    +

    The plugin will try to match on all traces in the input folder, including traces used for search results ( +of deferred extraction plugins). This means that it is impossible to search on traces that match the same deferred +extraction plugin, as it would create an infinite loop.

    +
    +

    Given the files in the example above, the test runner will invoke the extraction plugin one time:

    + + + + + + + + + + + +

    Input

    Result

    deferredExample.trace with data stream deferredExample.raw

    deferredExample.raw.DeferredPluginName.trace

    +
    +

    Warning

    +

    The search query should be written in HQL, as that is how Hansken will interpret it. However, the test +framework interprets the query using its HQL-lite interpreter. Therefore, not all queries will be supported.

    +
    +
    +
    +

    Trace format

    +

    Input and result traces both stored in a JSON structure. There is however a slight difference between the two: The +result trace may store additional values that are purely there for testing purposes. The input format will first be +discussed, followed by the result format.

    +
    +

    Input trace JSON format

    +

    Input traces start with a trace key, which contains a mapping of properties. The property names are split in a +dictionary structure. The example below shows a serialized trace with six properties: data.raw.mimeClass and the five +data types that are currently supported by the test-framework.

    +

    The data key defines the data-streams of the trace. When adding a data-stream make sure you also +add the corresponding input data file, as described above.

    +
    {
    +  "trace": {
    +    "data": {
    +      "raw": {
    +        "mimeClass": "text"
    +      }
    +    },
    +    "supported data types": {
    +      "Boolean": true,
    +      "Integer": 1,
    +      "Double": 0.1,
    +      "String": "a string",
    +      "StringList": [
    +        "a",
    +        "b",
    +        "c",
    +        "d"
    +      ]
    +    }
    +  }
    +}
    +
    +
    +
    +

    Warning

    +

    The extraction plugin SDK and the test framework have no knowledge of the +trace model. This means that when properties are used that don’t +comply with the trace model, this will not cause the test to fail, but it will fail when running your plugin in Hansken.

    +
    +
    +
    +

    Result trace JSON format

    +

    The result traces have the same format as the input traces, namely a trace key which contains the full input trace +with all its properties. However, the result traces may have two additional keys children and data (which are +explained in-depth below). These are added for testing purposes. If the plugin adds child traces +or writes data transformations to Hansken, this would normally not reflect on the JSON of the +trace. However, the test framework adds these to the result JSON structure to be able to test them.

    +

    Consequently, result traces are stored in a JSON structure that may consist of up to three parts, namely the always +present trace and the occasional children and data:

    +
      +
    • trace: The key trace contains a mapping of its properties, in exactly the same way as is done for input traces.

    • +
    • children: :ref:Child traces <child traces> that have been created by the plugin during the test are stored under a +reserved field children, which is a list of traces. The example trace below contains a child trace with a property +name.

    • +
    • data: Data transformations that have been created by the plugin during the test are +stored under a reserved field data. For each data-stream type there is a descriptor field describing the data +transformation in a JSON format. The example trace has a ranged data transformation for the raw data-stream. Note that +this data is entirely different from the data key that may be present inside the trace!

    • +
    +
    {
    +  "trace": {
    +    "data": {
    +      "raw": {
    +        "mimeClass": "text"
    +      }
    +    },
    +    "supported data types": {
    +      "Boolean": true,
    +      "Integer": 1,
    +      "Double": 0.1,
    +      "String": "a string",
    +      "List": [
    +        "a",
    +        "b",
    +        "c",
    +        "d"
    +      ]
    +    }
    +  },
    +  "children": [
    +    {
    +      "trace": {
    +        "name": "child trace 1"
    +      }
    +    }
    +  ],
    +  "data": {
    +    "raw": {
    +      "descriptor": "[{\"ranges\":[{\"length\":79,\"offset\":0}]}]"
    +    }
    +  }
    +}
    +
    +
    +
    +
    +
    +

    Testing exceptions

    +

    Some scenarios may throw exceptions and this can be part of your tests too. For example, an input file that has the +wrong format can be part of your integration tests. When an exception occurs during the test, it will be written to the +result file. This can be deliberately used to test exceptions. However, it is often impractical to match against a full +exception. For example, the row numbers in the exception are very much prone to change due to circumstances irrelevant +to the case being tested. Therefore, the testframework provides some options to match only on those parts of result +files that are relevant to the test.

    +

    The following sections will explain these partial result matchers using the following example exception:

    +
    {
    +  "class": "org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginException",
    +  "message": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris faucibus varius sodales."
    +}
    +
    +
    +
    +

    Leaving out the message

    +

    It is possible to leave of the message of the exception, which will still result in a valid result:

    +
    {
    +  "class": "org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginException"
    +}
    +
    +
    +
    +
    +

    The startsWith partial result matcher

    +

    The startsWith partial result matcher requires a string as a parameter. The result will be valid if the actual result +starts with this string.

    +
    {
    +  "class": "org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginException",
    +  "message.startsWith": "Lorem ipsum dolor sit amet, "
    +}
    +
    +
    +
    +
    +

    The containsInOrder partial result matcher

    +

    The containsInOrder partial result matcher requires a list of strings as a parameter. The result will be valid if +every string in the list can be found in that same order in the actual result.

    +
    {
    +  "class": "org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginException",
    +  "message.containsInOrder": [
    +    "Lorem ipsum dolor sit amet,",
    +    "consectetur adipiscing elit.",
    +    "Mauris faucibus varius sodales."
    +  ]
    +}
    +
    +
    +
    +
    +
    +
    +

    How to test your plugin

    +

    Running an integration test for an extraction plugin depends on the language in which the extraction plugin is built.

    +
    +

    Java

    +

    The Test Framework itself is built in Java. When building extraction plugins with Java, it can be incorporated in your +unit tests, as shown in Using the Test Framework in Java.

    +
    +
    +

    Python

    +

    The Python SDK also uses the Java based Test Framework. This is done by providing a wrapper to make calls to an included +Test Framework ‘jar’ file. See Advanced use of the Test Framework in Python for documentation +and examples on how to use FLITS for testing your Python plugin.

    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/concepts/traces.html b/0.7.0/dev/concepts/traces.html new file mode 100644 index 0000000..c3d6a51 --- /dev/null +++ b/0.7.0/dev/concepts/traces.html @@ -0,0 +1,344 @@ + + + + + + + Traces & Trace model — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Traces & Trace model

    +
    +

    Traces

    +

    Traces are structured data objects produced by tools/plugins during an extraction. A trace represents a piece of +information found in an evidence file.

    +

    The following figure shows the main elements of a trace. Each element is described in more detail in the following +paragraphs.

    +trace.svg +
    +

    Types and Properties

    +

    A trace has properties that describe the information of it by means of a property value. Trace properties are grouped by +a trace type. A trace can have multiple types.

    +

    All types and properties that can be set are defined in the Hansken trace model.

    +

    An example of a type is document, which could have the properties application and createdOn. The trace will have a +type document, and can the following properties with values:

    +
    document.application: Libre Office
    +document.createdOn: 2021-09-18 20:00:00
    +
    +
    +
    +
    +

    Intrinsic properties

    +

    A trace has several intrinsic properties. These are properties that are not related to a trace type. The intrinsic +properties available to extraction plugins are:

    +
      +
    • id: a unique identifier of the trace, generated when a trace is created

    • +
    • name: a name given to a trace when it is created

    • +
    • path: a logical path of the trace, of which the elements are the names of traces from the root trace until this +trace

    • +
    +
    +
    +

    Data streams

    +

    Typically, a trace represents a piece of data found in an evidence file. This data is part of the trace and available as +a data stream. A trace can have multiple data streams. Each data stream has a type. Data streams can also have +properties that apply to the data stream itself. The data stream properties are modeled as properties of the trace, in +the following pattern: +data.<datastreamtype>.propertyname (where <datastreamtype> is substituted by the actual type of the data stream).

    +

    The set of data stream types and data stream properties is fixed. All allowed types and properties are defined in the +Hansken trace model (see data).

    +

    An important data stream property is the fileType property. This property contains a textual description of the +detected file type for the data stream. An example of a fileType is ‘Adobe Pdf’. The fileType is a good candidate +to use in a extraction plugin ‘matcher’. This fileType is detected by Hansken using file type heuristics, which are +primarily based on the data stream bytes itself, and secondarily on other metadata such as a file extension. +(N.B. The fileType is detected in Hansken by the extraction tool Firefli.) For more information on how datastream +properties can be used for matching, see here.

    +

    Note that not all traces have data streams. In these cases it is a trace of meta-data derived from another trace.

    +

    Usually, each trace with data has a data stream of type raw. This data stream contains the bytes of the traces as they +were found when the trace was created. In some occasions, the raw data can be represented in a different form before it +can be processed further, for example if the data can be decoded or decrypted. Hansken tools and plugins can decode +the raw data stream to a standard UTF-8 data stream, or can decrypt the data if a decryption key is present. Hansken +tools and extraction plugins can store the new data at the new trace in a new data stream. This new data stream has a +different type than the raw type.

    +

    Examples in code can be found here:

    +
      +
    • Adding a Datastream Java

    • +
    • Adding a Datastream Python

    • +
    +
    +
    +

    Child traces

    +

    A trace can have child traces. For example, a trace of type archive can have children, where each child is a trace +that represents an entry in the archive.

    +

    With an extraction plugin it is possible to create child traces for a trace that is being processed. New properties, +data streams, and other child traces can be set on the new child traces. When a child trace is created, the plugin +should provide a name for the child trace. The id of the child trace is generated, in the following +form: parenttraceid-childnumber. For example, if the parent has an id 0-0-0-0-0:0-9, the first child gets the +id 0-0-0-0-0:0-9-1, the second child gets the id 0-0-0-0-0:0-9-2, and so on.

    +

    Note that a trace does not have (direct) access to its parent trace.

    +
    +
    +

    Trace property types

    +

    The SDK supports the following property types for traces:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Java

    Python

    binary

    byte[]

    bytes / bytearray

    boolean

    boolean

    bool

    integer

    int / long

    int

    real

    float / double

    float

    string

    String

    str

    date & time

    Date

    datetime

    list

    List

    list / tuple

    mapping

    Map

    dict

    location

    LatLong

    GeographicLocation

    vector

    Vector

    Vector

    tracelet

    see Tracelets below

    see Tracelets below

    +

    Both location and vector types are available from the SDK, Java package org.hansken.plugin.extraction.api or Python module hansken.util.

    +
    +

    Vector

    +

    A vector is a data type that can be used to store points in n-dimensional space as an array of floating point values. +Once indexed, the vectors can then be used in a gui or other client to search for traces that have a nearby vectors. +For example, it is possible to use a neural network that provides embeddings of human faces as vectors. Once indexed, +the vectors can then be used to find pictures with similar faces. To do this, the search rest api can be used to +sort by the euclidean- or manhattan distance, or cosine similarity to a given vector.

    +
    +
    +

    Tracelets

    +

    A Tracelet is a bundle of property values that belong to a single type. It is a property on a trace that can have +multiple properties itself, making it a list of key/value pairs. The API doesn’t specify the cardinality, but the +implementation is limited to cardinality ‘Few’. In Hansken these are called FVT’s (Few Valued Types).

    +
    +

    Note

    +

    MVT’s (Many Valued Types) are currently not supported in the SDK and will be added in a future release.

    +
    +

    An example of a tracelet is the prediction property, which describes a category or class a trace belongs to. It is +possible for a trace to have multiple predictions. Therefore prediction is a tracelet. Other examples of +tracelets are identity and collection.

    +

    Examples in code can be found here:

    +
      +
    • Adding tracelets in Java

    • +
    • Adding tracelets in Python

    • +
    +
    +
    +
    +
    +

    Hansken trace model

    +

    All traces in Hansken are based on a specific version of the trace model, and must comply to that version of the trace +model. This is a nested data structure composed of origins, categories, types and properties.

    +

    All non-inrinsic trace properties are optional and are grouped by type. These types are defined under the trace +model section ‘categories’. Every category has a list of allowed types. When a trace is identified as being a +document, it will get this set of predefined document properties. Trace types can have different origins. The +possible origins are defined in the trace model section ‘origins’. An example of this is the processed types that are +always generated by the system during an extraction.

    +

    The details of the current trace model can be retrieved using the /tracemodel +REST call on the Gatekeeper endpoint of Hansken, or check the Hansken Documentation on the trace model.

    +
    +

    Trace model and the extraction plugin SDK

    +
    +

    Warning

    +

    The extraction plugin SDK has no knowledge of the trace model

    +
    +

    The Extraction Plugins SDK has no knowledge of the trace model at this time. It is however possible to create new traces +with plugins. If any newly created Traces don’t comply to the model, Hansken will not accept them and mark the plugin +execution as failed. The Extraction Plugins SDK and the provided Test Framework don’t check this. +Please make sure to use the right naming when creating new Traces, as provided by the trace model.

    +

    If an erroneous trace property is set, Hansken will show an error. The error can be found in the Hansken Expert UI +interface by double-clicking on the trace. Then the trace details screen will be opened and the error will be displayed +as follows:

    +../../_images/toolrun_error.png +

    This error describes that a property does not exist in the trace model. To get more information about the error, the +extraction log can be viewed. In the extraction log you have to search +for java.lang.IllegalArgumentException: no such type to find out which property is not supported by the trace model.

    +

    In the example extraction log below, the property this_property_does_not_exist could not be found 681 times.

    +
    Cumulative warnings, based on the message without numbers, uuids and trace objects. Only showing full message for first warning of this type.
    +      Count | Key                                                   | Message
    +        681 | org.hansken.ep.shade.io.grpc.StatusRuntimeException   | CANCELLED: Cancelled by client with StreamObserver.onError(); org.hansken.ep.shade.io.grpc.StatusRuntimeException: ABORTED: java.lang.IllegalArgumentException: no such type: this_property_does_not_exist
    +          7 | java.lang.IllegalStateException                       | call was cancelled
    +          1 | org.hansken.ep.shade.io.grpc.StatusRuntimeException   | UNAVAILABLE: HTTP/2 error code: NO_ERROR Received Rst Stream
    +
    +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/examples.html b/0.7.0/dev/examples.html new file mode 100644 index 0000000..ed07e90 --- /dev/null +++ b/0.7.0/dev/examples.html @@ -0,0 +1,126 @@ + + + + + + + Examples — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Examples

    +

    Extraction Plugin Examples in both Java and Python can be found in the +Hansken Extraction Plugin Examples Repository +hosted on Github.

    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/faq.html b/0.7.0/dev/faq.html new file mode 100644 index 0000000..71bfdbb --- /dev/null +++ b/0.7.0/dev/faq.html @@ -0,0 +1,195 @@ + + + + + + + Frequently Asked Questions — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Frequently Asked Questions

    +
    +

    How can I access Hansken developer community

    +

    You will first need git access to the Hansken developer community. Here you can find started guides and examples. If you +have no access yet, you can get access by following the next steps:

    +
      +
    1. Sign up at git.eminjenv.nl

    2. +
    3. After you have created your account, you should request access to the ” +Hansken Community” group . Do this by contacting your organisations Hansken business owner. If you don’t know who to +contact as your business owner, please read the Contact page.

    4. +
    +
    +
    +

    Why use Extraction Plugins?

    +

    Extraction plugins enable you to run your own extraction code in Hansken.

    +
      +
    • Your plugin runs during extraction

    • +
    • Extraction plugins are faster than Hansken.py

    • +
    • Multiple programming languages are supported

    • +
    • Scalability: Extraction plugins can be scaled flexibly on a kubernetes cluster

    • +
    +
    +
    +

    What programming languages are supported?

    +

    The SDK contains an API and tools to write a Hansken extraction plugin in Java or Python. The Java API can be used to +develop extraction plugins in JVM-compatible languages, such as Scala and Kotlin.

    +
    +
    +

    Will you support language foobar?

    +

    Probably not. It takes time and effort to create a proper SDK. If you think there is a good use case to support +language foobar, and there is gRPC support, feel free to contact us. We can discuss the options to add support for +Hansken extraction plugins with foobar.

    +

    Under the hood, extraction plugins use gRPC to communicate with Hansken. In theory, all programming languages that have +a gRPC implementation can be used to write Hansken extraction plugins.

    +
    +
    +

    Can I reuse or modify the Extraction Plugins SDK?

    +

    The SDK is distributed under the Apache 2.0 License, see the LICENSE file in the SDK for more details.

    +
    +
    +

    Can I use a plugin that someone else wrote?

    +

    Yes, at your own risk. The Hansken Team does not take responsibility for code written by third parties in the form of +Extraction Plugins.

    +
    + +
    +

    How safe are Extraction Plugins?

    +

    We are doing everything to make sure Extraction Plugins are as safe as possible, however note that the Extraction Plugin +SDK is still in beta. Use it at your own risk. For more information on security see Isolation.

    +
    +
    +

    Can my Extraction Plugin be embedded into Hansken for performance reasons?

    +

    Embedding an Extraction Plugin into Hansken requires access to the Hansken source code. If you have access to the source +code then please ../contact us for assistance. Please note that embedded Extraction Plugins are not officially +supported.

    +

    If you do not have access to the Hansken source code, then please contact your own Business Owner, and ask them to +contact the Hansken Team.

    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/introduction.html b/0.7.0/dev/introduction.html new file mode 100644 index 0000000..c32ec5c --- /dev/null +++ b/0.7.0/dev/introduction.html @@ -0,0 +1,175 @@ + + + + + + + Introduction — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Introduction

    +

    The Extraction Plugin Software Development Kit can be used to develop a Hansken +extraction plugin.

    +

    Hansken is designed to give access to and insight in digital data and traces originating from seized and demanded +digital material. One aspect of the Hansken platform is the extraction engine (extraction framework). The extraction +engine contains digital forensics knowledge, which is used to find traces in digital material. With extraction plugins +case investigators can add new digital forensics knowledge to the extraction framework. In this way, Hansken is enabled +to understand new digital formats, and thus is able to find new types of traces in the seized and demanded material.

    +

    Examples of digital forensics knowledge that can be added to Hansken with extraction plugins:

    +
      +
    • new file formats (e.g. a new crypto currency wallet)

    • +
    • combine traces to find new information (e.g. use a windows registry entry required to read a file from disk)

    • +
    • apply algorithms on traces (e.g. speech to text from audio files)

    • +
    +

    The primary goals of this SDK are:

    +
      +
    • to make it as easy as possible to add new digital forensics knowledge to Hansken.

    • +
    • be able to share digital forensics knowledge with other Hansken community members

    • +
    +
    +

    Software Development Kit (SDK)

    +

    In order to create extraction plugins, the Hansken project maintains an Extraction Plugin Software Development Kit +(SDK).

    +

    This SDK contains the following elements:

    +
      +
    • Java API and tooling, to be able to write an extraction plugin with the Java +programming language

    • +
    • Python API and tooling, to be able to write an extraction plugin with Python +programming language

    • +
    • Test framework, to be able to test extraction plugins before they are used production

    • +
    • Documentation for plugin developers to help understand the SDK and all its facets (this documentation)

    • +
    • Examples

    • +
    +
    +
    +

    Development steps of a plugin

    +

    To create a plugin, the plugin developer could follow the following steps. Detailed information per step will be added +later to the documentation.

    +
      +
    1. Create a new empty plugin

    2. +
    3. Implement the plugin logic

    4. +
    5. Verify the plugin using the test framework, using reference data (test data) and +expected output

    6. +
    7. Test the plugin in Hansken

    8. +
    9. Use the plugin in an actual case

    10. +
    11. Share the plugin with the Hansken community, so other case investigations can benefit from the plugin as well ( +optional, but encouraged)

    12. +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/java.html b/0.7.0/dev/java.html new file mode 100644 index 0000000..199df96 --- /dev/null +++ b/0.7.0/dev/java.html @@ -0,0 +1,144 @@ + + + + + + + Java — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/0.7.0/dev/java/api_changelog.html b/0.7.0/dev/java/api_changelog.html new file mode 100644 index 0000000..03ea05b --- /dev/null +++ b/0.7.0/dev/java/api_changelog.html @@ -0,0 +1,367 @@ + + + + + + + Java API Changelog — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Java API Changelog

    +

    This document summarizes all important API changes in the Extraction Plugin API. This document only shows changes that +are important to plugin developers. For a full list of changes per version, please refer to the general +changelog.

    +
    +

    0.7.0 +

    +
      +
    • Escaping the / character in matchers is optional. +This simplifies and aims for better HQL and HQL-Lite compatability. +See for more information and examples the HQL-Lite syntax documentation.

      +

      Examples:

      +
        +
      • file.path:\\/Users\\/*\\/AppData -> file.path:/Users/*/AppData

      • +
      • registryEntry.key:\\/Software\\/Dropbox\\/ks*\\/Client-p -> registryEntry.key:/Software/Dropbox/ks*/Client-p

      • +
      +
    • +
    • Hansken returns file.path properties as a String property, instead of a List<String>. +Example: trace.get("file.path") now returns "/dev/null", this was ["dev", "null"].

    • +
    +
    +
    +

    0.6.1

    +
      +
    • The JAVA SDK is now distributed through maven central instead of the Hansken community.

    • +
    +
    +
    +

    0.6.0

    +
    +

    Warning

    +

    It is highly recommended to upgrade your plugin to this new version. +See the migration steps below.

    +
    +
      +
    • Extraction plugin container images are now labeled with PluginInfo. This +allows Hansken to efficiently load extraction plugins.

    • +
    • By default, extraction plugin version is managed in the plugin’s pom.xml. +The .pluginVersion(..) can be removed from the PluginInfo builder.

    • +
    • Migration steps from earlier versions – for plugins that use the Java +extraction plugin SuperPOM:

      +
        +
      1. Update the SDK version in your pom.xml

      2. +
      3. If you come from a version prior to 0.4.0, or if you use a plugin name +instead of a plugin id in your pluginInfo(), switch to the plugin id style +(read instructions for version 0.4.0)

      4. +
      5. Set your plugin version in your project’s pom.xml, and remove the +following from your PluginInfo.Builder:

        +
        .pluginVersion(...)
        +
        +
        +
      6. +
      7. Update your build scripts to build your plugin (Docker) container image. +You should build your plugin container image with the following command:

        +
        mvn package docker:build`
        +
        +
        +

        This will generate a plugin image:

        +
          +
        • The extraction plugin is added to your local image registry +(docker images),

        • +
        • The image name is extraction-plugin/PLUGINID, e.g. +extraction-plugin/nfi.nl/extract/chat/whatsapp,

        • +
        • The image is tagged with two tags: latest, and your plugin version.

        • +
        +

        Nb. If Docker is not available in your environment, podman can be used +as an alternative. See packaging for more +details.

        +
      8. +
      +
    • +
    +
    +
    +

    0.5.0

    +
      +
    • Add new tracelet api Trace.addTracelet(type, consumer). +It can be used like this:

      +
      trace.addTracelet("prediction", tracelet -> tracelet
      +     .set("type", "classification")
      +     .set("label", "label")
      +     .set("confidence", 0.8f)
      +     .set("embedding", Vector.of(1,2,3))
      +     .set("modelName", "yolo")
      +     .set("modelVersion", "2.0"));
      +
      +
      +
    • +
    • Deprecate Trace.addTracelet(Trace)

    • +
    • Support vector data type in trace properties.

    • +
    +
    +
    +

    0.4.13

    +
      +
    • When writing input search traces for tests, it is no longer required to explicitly set an id property. +These are automatically generated when executing tests.

    • +
    +
    +
    +

    0.4.7

    +
      +
    • A new convenience method id(String, String, String) is added to the PluginInfo builder. This removes some +boilerplate code when setting the pluginId. More details on the plugin naming conventions can be found at the +Plugin naming convention section.

      +
      PluginInfo.builderFor(this)
      +          .id("nfi.nl", "extract", "TestPlugin") // new style
      +          .id(new PluginId("nfi.nl", "extract", "TestPlugin")) // old style, but also works
      +          ...
      +
      +
      +
    • +
    +
    +
    +

    0.4.6

    +
      +
    • It is now possible to specify maximum system resources in the PluginInfo. To run a plugin with 0.5 cpu (= 0.5 +vCPU/Core/hyperthread) and 1 gb memory, for example, the following configuration can be added to PluginInfo:

      +
      PluginInfo.builderFor(this)
      +    ...
      +    .pluginResources(PluginResources.builder()
      +        .maximumCpu(0.5f)
      +        .maximumMemory(1000)
      +        .build())
      +    .build();
      +
      +
      +
    • +
    +
    +
    +

    0.4.0

    +
      +
    • Extraction Plugins are now identified with a PluginInfo.PluginId containing a domain, category and name. The +method PluginInfo.name(pluginName) has been replaced by PluginInfo.id(new PluginId(domain, category, name). More +details on the plugin naming conventions can be found at the Plugin naming convention section.

    • +
    • PluginInfo.name() is now deprecated (but will still work for backwards compatibility).

    • +
    • A new license field PluginInfo.license has also been added in this release.

    • +
    • The following example creates a PluginInfo for a plugin with the name TestPlugin, licensed under +the Apache License 2.0 license:

      +
      PluginInfo.builderFor(this)
      +          .id(new PluginId("nfi.nl", "extract", "TestPlugin")) // id.domain: nfi.nl, id.category: extract, id.name: TestPlugin
      +          // .name("TestPlugin") // no longer supported
      +          .pluginVersion("0.4.1")
      +          .author(Author.builder()...build())
      +          .description("A plugin for testing.")
      +          .maturityLevel(MaturityLevel.PROOF_OF_CONCEPT)
      +          .hqlMatcher("*")
      +          .webpageUrl("https://www.hansken.org")
      +          .license("Apache License 2.0")
      +          .build();
      +
      +
      +
    • +
    +
    +
    +

    0.3.0

    +
      +
    • Extraction Plugins can now create new datastreams on a Trace through data transformations. Data transformations +describe how data can be obtained from a source.

      +

      An example case is an extraction plugin that processes an archive file. The plugin creates a child trace per entry in +the archive file. Each child trace will have a datastream that is a transformation that marks the start and length of +the entry in the original archive data. By just describing the data instead of specifying the actual data, a lot of +space is saved.

      +

      Although Hansken supports various transformations, the Extraction Plugins SDK for now only supports ranged data +transformations. Ranged data transformations define data as a list of ranges, each range with an offset and length in +a bytearray.

      +

      The following example sets a new datastream with dataType html on a trace, by setting a ranged data transformation:

      +
      trace.setData("html", RangedDataTransformation.builder().addRange(offset, length).build());
      +
      +
      +

      The following example creates a child trace and sets a new datastream with dataType raw on it, by setting a ranged +data transformation with two ranges:

      +
      trace.newChild(format("lineNumber %d", lineNumber), child -> {
      +    child.setData("raw", RangedDataTransformation.builder()
      +      .addRange(10, 20)
      +      .addRange(50, 30)
      +      .build());
      +});
      +
      +
      +

      More detailed documentation will follow in an upcoming SDK release.

      +
    • +
    +
    +
    +

    0.2.0

    +
    +

    Warning

    +

    This is an API breaking change. Plugins created with an earlier version of the extraction plugin SDK are +not compatible with Hansken that uses 0.2.0 or later.

    +
    +
      +
    • Introduced a new extraction plugin type DeferredExtractioPlugin. Deferred Extraction plugins can be run at a +different extraction stage. This type of plugin also allows accessing other traces using the searcher.

    • +
    • The class ExtractionContext has been renamed to DataContext. The new name DataContext represents the class +contents better. Plugins have to update matching import statements and the type in ExtractionPlugin.process() +implementation in the same way. This change has no functional side effects.

      +

      Old:

      +
      import org.hansken.plugin.extraction.api.ExtractionContext;
      +
      +@Override
      +
      +public void process(final Trace trace, final ExtractionContext context) throws IOException {
      +
      +}
      +
      +
      +

      New:

      +
      import org.hansken.plugin.extraction.api.DataContext;
      +
      +@Override
      +public void process(final Trace trace, final DataContext dataContext) throws IOException {
      +
      +}
      +
      +
      +
    • +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/java/debugging.html b/0.7.0/dev/java/debugging.html new file mode 100644 index 0000000..a4e748e --- /dev/null +++ b/0.7.0/dev/java/debugging.html @@ -0,0 +1,275 @@ + + + + + + + How to debug an Extraction Plugin — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    How to debug an Extraction Plugin

    +

    Debugging is the art of removing bugs — hopefully quickly.

    +
    +

    Locally

    +

    To debug a plugin locally, it is recommended to start the plugin via the IDE by running the integration test. This has +the advantage that breakpoints can easily be put in the code instead of printing log statements, for example.

    +
    +

    Logging

    +

    The logging of the extraction plugin is displayed in the console.

    +
    +
    +
    +

    Locally with Docker

    +

    Debugging an extraction plugin via docker is a bit trickier. Java has the advantage that remote debugging is already +baked in.

    +

    Using Java Remote Debug with Docker containers requires 3 distinct steps:

    +
      +
    1. Build a Docker image

    2. +
    3. Run the Docker image with specific Java tool options

    4. +
    5. Setting breakpoints in your code

    6. +
    +
    +

    Build a Docker image

    +

    If the Docker image is not built, run the following command to build the Docker image:

    +
    mvn package docker:build
    +
    +
    +
    +
    +

    Run the Docker image with specific Java tool options

    +

    In Java, the remote debug functionality is not enabled by default. To enable the remote debug functionality, the +following environments variable must be set in the Docker container:

    +
    JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
    +
    +
    +

    This environment variable allows the debugger to connect to the debuggee (application being debugged). To start the +Docker image with the JAVA_TOOL_OPTIONS environment variable, the following command can be used:

    +
    docker run -p 5005:5005 -e JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" your_extraction_plugin_name
    +
    +
    +

    The next step is to attach the debugger to the debuggee. +For Intellij, the instructions are clearly described on the following +page: Tutorial: Remote debug

    +
    +
    +

    Setting breakpoints in the code

    +

    The last step is to add breakpoints in the code.

    +
    +
    +

    Logging in Docker

    +

    The logging of the extraction plugin is displayed in the console after running the docker run command. In addition, +the logging is also displayed in the IntelliJ console while debugging.

    +
    +
    +
    +

    Kubernetes

    +

    In kubernetes it is currently not possible to debug via Java Remote Debug because:

    +
      +
    • no debug ports are published;

    • +
    • the container was not started with the environment variable JAVA_TOOL_OPTIONS so debugging is not enabled.

    • +
    +
    +

    Logging in Kubernetes

    +

    If there is authorization to the kubernetes cluster, the logging can be viewed with the following command:

    +
    kubectl logs -f hansken-extraction-plugins/your_extraction_plugin_pod
    +
    +
    +
    +
    +
    +

    Debug HQL

    +

    An HQL query can be debugged by overriding the isVerboseLoggingEnabled() method of the ExtractionPluginFlits class. +The example below shows an example of an embedded FLITS test with verbose logging enabled.

    +
    public class TestPluginFlitsIT extends EmbeddedExtractionPluginFlits {
    +
    +    @Override
    +    public Path testPath() {
    +        return srcPath("integration/inputs/plugin");
    +    }
    +
    +    @Override
    +    public Path resultPath() {
    +        return srcPath("integration/results/embedded/plugin");
    +    }
    +
    +    @Override
    +    protected ExtractionPlugin pluginToTest() {
    +        return new TestPlugin();
    +    }
    +
    +    @Override
    +    public boolean regenerate() {
    +        return true;
    +    }
    +
    +    @Override
    +    protected boolean isVerboseLoggingEnabled() {
    +        return true;
    +    }
    +}
    +
    +
    +

    The following output will then be displayed in the console:

    +
    HQL match found for:
    +$data.type=jpg
    +With trace:
    +dataType=jpg
    +types={file, data}
    +properties={data.raw.mimeType=image/jpg, path=/test-input-trace, file.name=image.jpg, name=test-input-trace, id=0}
    +
    +
    +

    If the HQL query contains an error, it will be shown in the generated test results. An example of an invalid query +is $data.mimeType=image/jpg (slash not escaped). This query will produce an error like the one shown below.

    +
    {
    +  "class": "org.hansken.plugin.extraction.hql_lite.lang.ParseException",
    +  "message": "HqlLiteHumanQueryParser: line 1:20 token recognition error at: '/jpg'"
    +}
    +
    +
    +
    +

    Note

    +

    The error is only shown in the generated trace, so to find out the ParseException override +the regenerate() method from Flits and then let this method return true.

    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/java/javadoc.html b/0.7.0/dev/java/javadoc.html new file mode 100644 index 0000000..cba206e --- /dev/null +++ b/0.7.0/dev/java/javadoc.html @@ -0,0 +1,134 @@ + + + + + + + Javadoc — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Javadoc

    +

    Visit the Javadoc of the Extraction Plugins SDK API.

    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/java/packaging.html b/0.7.0/dev/java/packaging.html new file mode 100644 index 0000000..e56a4c4 --- /dev/null +++ b/0.7.0/dev/java/packaging.html @@ -0,0 +1,164 @@ + + + + + + + Packaging — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Packaging

    +

    Extraction plugins are packaged as OCI images (also known as Docker images). +The OCI images are labeled with the PluginInfo. +To automate packaging of a Java plugin and labeling the OCI image, the Extraction Plugin SuperPom has been configured to automate this for you.

    +

    If your project uses the Extraction Plugin SuperPom (see Prerequisites), Packaging an extraction plugin is handled by Maven. +To package your plugin into a container image, the following command can be used:

    +
    mvn package docker:build
    +
    +
    +

    This will generate a plugin image:

    +
      +
    • The extraction plugin is added to your local image registry +(docker images),

    • +
    • The image name is extraction-plugin/PLUGINID, e.g. +extraction-plugin/nfi.nl/extract/chat/whatsapp,

    • +
    • The image is labeled with two tags: latest, and your plugin version.

    • +
    +

    It is possible to apply extra arguments to the docker command as described here. +For example, to specify a proxy, use the following command:

    +
    mvn package docker:build -Ddocker.buildArg.https_proxy=https://proxy:8001
    +
    +
    +

    Once your plugin is packaged, it can be published or ‘uploaded’ to Hansken. +See “Upload the plugin to Hansken” for instructions.

    +

    Note: if your build environment does not have Docker available, you can use +podman as an alternative. Install podman on your machine +or build agent, and run the following commands before invoking the +mvn package docker:build command:

    +
    podman system service --time=0 unix:/run/user/$(id -u)/podman/podman.sock &
    +export DOCKER_HOST="unix:/run/user/$(id -u)/podman/podman.sock"
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/java/prerequisites.html b/0.7.0/dev/java/prerequisites.html new file mode 100644 index 0000000..0a17cff --- /dev/null +++ b/0.7.0/dev/java/prerequisites.html @@ -0,0 +1,175 @@ + + + + + + + Prerequisites — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Prerequisites

    +

    Required software:

    +
      +
    • Java 11 or higher

    • +
    • Docker for packaging and publishing plugins
      +(or use a Docker alternative such as podman)

    • +
    • Maven (recommended, build automation tool)

    • +
    +

    Required dependencies:

    +
      +
    • All required project dependencies to build extraction plugins are published on the public Maven Central, under org.hansken.plugin.extraction:plugin-super-pom. +For maven based extraction plugins, the following pom.xml snippet can be used as basis of a plugin:

      +
      <?xml version="1.0" encoding="UTF-8"?>
      +<project xmlns="http://maven.apache.org/POM/4.0.0"
      +    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      +    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      +    <modelVersion>4.0.0</modelVersion>
      +
      +    <parent>
      +        <groupId>org.hansken.plugin.extraction</groupId>
      +        <artifactId>plugin-super-pom</artifactId>
      +        <version>SET_THE_SDK_VERSION_HERE</version>
      +    </parent>
      +
      +    <artifactId>CHOOSE_YOUR_ARTIFACTID_HERE</artifactId>
      +    <version>SET_THE_PLUGIN_VERSION_HERE</version>
      +
      +    <licenses>
      +        <license>
      +            <name>The Apache Software License, Version 2.0</name>
      +            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
      +            <distribution>repo</distribution>
      +        </license>
      +    </licenses>
      +
      +    <properties>
      +        <mainClass>SET_THE_PLUGIN_MAIN_CLASS_HERE</mainClass>
      +    </properties>
      +</project>
      +
      +
      +
    • +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/java/snippets.html b/0.7.0/dev/java/snippets.html new file mode 100644 index 0000000..157b751 --- /dev/null +++ b/0.7.0/dev/java/snippets.html @@ -0,0 +1,374 @@ + + + + + + + Java code snippets — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Java code snippets

    +

    This page contains Java code snippets for common patterns that will be used when writing a plugin.

    +
    +

    RandomAccessData as InputStream

    +

    In Java, InputStream is a common type to pass data to another class or method. The SDK provides a simple utility to +use a RandomAccessData as InputStream.

    +

    Add the following import to your code:

    +
    import org.hansken.plugin.extraction.core.data.RandomAccessDatas;
    +
    +
    +

    Next we can create an InputStream from the RandomAccessData as shown in the following snippet. Note that the +InputStream is created using a try-with-resources-statement. This ensures that the InputStream is correctly closed +when the InputStream is no longer required.

    +
    RandomAccessData traceData=...;
    +    try(InputStream asInputStream=RandomAccessDatas.asInputStream(traceData)){
    +    // use the InputStream here
    +    }
    +
    +
    +

    Notes:

    +
      +
    • the created InputStream is not thread-safe,

    • +
    • the created InputStream changes state in the provided RandomAccessData +(e.g. when data is read, the position updated in both the InputStream and +the RandomAccessData instances),

    • +
    • for more details on the implementation of the InputStream, refer to the RandomAccessDataInputStream JavaDoc.

    • +
    +
    +
    +

    Adding tracelets

    +

    In the following Java example, a “classification” tracelet is added to a trace. The tracelet consists +of a list of four properties, namely “class”, “confidence”, “modelName” and “modelVersion”.

    +
    trace.addTracelet("prediction", tracelet -> tracelet
    +     .set("type", "classification")
    +     .set("class", "telephone")
    +     .set("label", "label")
    +     .set("confidence", 0.8f)
    +     .set("embedding", Vector.of(1,2,3))
    +     .set("modelName", "yolo")
    +     .set("modelVersion", "2.0"));
    +
    +
    +

    or

    +
    trace.addTracelet(new Tracelet("prediction", List.of(
    +    new TraceletProperty("prediction.type","classification"),
    +    new TraceletProperty("prediction.class","telephone"),
    +    new TraceletProperty("prediction.label","label"),
    +    new TraceletProperty("prediction.confidence",0.8f))),
    +    new TraceletProperty("prediction.embedding", Vector.of(1,2,3)),
    +    new TraceletProperty("prediction.modelName","yolo"),
    +    new TraceletProperty("prediction.modelVersion","2.0"));
    +
    +
    +
    +
    +

    Adding data to a trace

    +

    Traces can have data attached to them. See Data streams for more information. +The following two snippets demonstrate how to add data to a trace.

    +

    It is currently not possible to verify that a specific data stream is already set or not.

    +
    +

    Data Transformations

    +

    The most efficient way to add data to a trace is using data transformations. +See Data Transformations for more details.

    +

    The following example sets a new data stream with dataType html on a trace, by setting a ranged data transformation:

    +
    trace.setData("html", RangedDataTransformation.builder().addRange(offset, length).build());
    +
    +
    +

    The following example creates a child trace and sets a new datastream with dataType raw on it, by setting a ranged +data transformation with two ranges:

    +
    trace.newChild(format("lineNumber %d", lineNumber), child -> {
    +    child.setData("raw", RangedDataTransformation.builder()
    +                  .addRange(10, 20)
    +                  .addRange(50, 30)
    +                  .build());
    +});
    +
    +
    +
    +
    +

    Blobs

    +

    It is not always possible to create a transormation for the data that has to be +added to a trace. For example the data is a result of a computation, and not +a direct subset of another data stream..

    +

    The following examples show how to creates a new data stream of dataType raw on a trace.

    +

    In case all data is stored in a byte[], we can add the byte array to the data stream with:

    +
    final byte[] rawBytes = {.....};
    +trace.setData("raw", writer -> writer.write(rawBytes));
    +
    +
    +

    Alternatively, if the data is available in an InputStream the data can be added with:

    +
    final InputStream inputStream = ...;
    +trace.setData("raw", inputStream);
    +
    +
    +
    +
    +
    +

    Specifying system resources

    +

    In the PluginInfo you can specify maximum system resource metrics for a plugin. These are used for scaling the +number of pods as described here. To run a plugin with 0.5 cpu (= +0.5 vCPU/Core/hyperthread) and 1 gb memory, for example, the following configuration can be added to PluginInfo:

    +
    PluginInfo.builderFor(this)
    +    ...
    +    .pluginResources(PluginResources.builder()
    +    .maximumCpu(0.5f)
    +    .maximumMemory(1000)
    +    .build())
    +    .build();
    +
    +
    +
    +
    +

    Deferred Extraction Plugins

    +

    Using a deferred plugin requires inheriting the DeferredExtractionPlugin base class. This allows access to +a TraceSearcher object in the process function to search for traces.

    +
    public class ExampleDeferred extends DeferredExtractionPlugin {
    +    @Override
    +    public PluginInfo pluginInfo();
    +
    +    @Override
    +    public void process(final Trace trace, final ExtractionContext context,
    +                        final TraceSearcher searcher) {
    +        final SearchResult result = searcher.search("file.extension=asc", 10);
    +    }
    +}
    +
    +
    +

    The search method accepts a HQL query and a count, which represents the maximum number of traces to return. It may +be useful to specifically search for traces from the image being extracted. Add "image:" + trace.get("image") to +your query. The query of the provided example could be extended like this: +"file.extension = asc AND image:" + trace.get("image").

    +

    The traces contained in the SearchResult are returned as a stream.

    +
    final Stream<Trace> stream = result.getTraces();
    +stream.limit(5);
    +
    +
    +
    +
    +

    Logging

    +

    The logging is provided by Log4j 2 with a SLF4J binding. The Log4j 2 SLF4J binding allows applications coded to the +SLF4J API to use Log4j 2 as the implementation.

    +
    +

    Usage

    +

    Here is an example illustrating how to log something with SLF4J. It begins by getting a logger with the name “LOG”. This +logger is in turn used to log the message I'm logging a variable 1234!.

    +
    import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +public class Example {
    +    private static final Logger LOG = LoggerFactory.getLogger(Example.class);
    +
    +    public void example() {
    +        final int aNumber = 1234;
    +        // logs to console: I'm logging a variable 1234!
    +        LOG.info("I'm logging a variable {}!", aNumber);
    +    }
    +}
    +
    +
    +
    +
    +

    Customize logging

    +

    It’s easy to change the logging format with a file called log4j2.xml. If desired, this file must be in the resources +folder, for example src/main/resources/log4j2.xml

    +
    <?xml version="1.0" encoding="UTF-8"?>
    +<configuration>
    +    <appenders>
    +        <console name="stdout" target="SYSTEM_OUT">
    +            <patternLayout
    +                    pattern="%-5p|%d{yyyy-MM-dd HH:mm:ss}|%-20.20t|%-32.32c{1}|%m%n"/>
    +        </console>
    +    </appenders>
    +    <loggers>
    +        <root level="info">
    +            <appenderRef ref="stdout"/>
    +        </root>
    +    </loggers>
    +</configuration>
    +
    +
    +
    +

    Warning

    +

    Be careful with logging sensitive information.

    +
    +
    +

    Note

    +

    More information about customizing the logging can be found here.

    +
    +
    +

    Note

    +

    The default logger is pre-configured to log INFO to STDOUT (see the configuration above)

    +
    +
    +

    Note

    +

    Log4j 2 supports various logging formats, including xml, yaml, json, properties, etc. +Currently, only the xml format is supported.

    +
    +
    +

    Note

    +

    Contact your Hansken administrator for more information on where to find logs for your Hansken environment.

    +
    +
    +
    +
    +

    [EXPERIMENTAL FEATURE] Adding previews to a trace

    +
    +

    Warning

    +

    This is an experimental feature, which might change or get removed in future releases.

    +
    +

    Example:

    +
    public class ExamplePlugin extends ExtractionPlugin {
    +  @Override
    +  public PluginInfo pluginInfo();
    +
    +  @Override
    +  public void process(final Trace trace, final DataContext context) {
    +    final byte[] previewData;
    +    // set the preview data for the image/png MIME-type
    +    trace.set("preview.image/png", previewData);
    +    trace.set("preview.image/png", previewData);
    +  }
    +}
    +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/java/testing.html b/0.7.0/dev/java/testing.html new file mode 100644 index 0000000..e675c1c --- /dev/null +++ b/0.7.0/dev/java/testing.html @@ -0,0 +1,291 @@ + + + + + + + Using the Test Framework in Java — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Using the Test Framework in Java

    +

    This section assumes you use the same setup as is used in the +Extraction Plugin Examples.

    +
    +

    Prerequisites

    +

    Java Plugins can use the plugin-super-pom as maven parent, which makes sure the +FLITS Test Framework is included in the build.

    +
    <parent>
    +    <groupId>org.hansken.plugin.extraction</groupId>
    +    <artifactId>plugin-super-pom</artifactId>
    +    <version>0.4.3</version>
    +</parent>
    +
    +
    +
    +
    +

    Embedded Testing versus Remote Testing

    +

    There are ways of integration testing a plugin with the Test Framework:

    +
      +
    • Embedded testing: Here the plugin is run directly from a JUnit test without using the gRPC layer.

    • +
    • Remote testing: Here the test will start an ExtractionPluginServer that will serve the plugin. All communication +is between server and plugin is done using gRPC.

    • +
    +

    See below for an example of each way of testing. +The Extraction Plugin Examples contains many +more examples.

    +
    +

    Embedded Testing example

    +

    Embedded tests can be run as a unit test.

    +
    import static nl.minvenj.nfi.flits.util.FlitsUtil.srcPath;
    +
    +import java.nio.file.Path;
    +
    +import org.hansken.plugin.extraction.api.ExtractionPlugin;
    +import org.hansken.plugin.extraction.test.EmbeddedExtractionPluginFlits;
    +
    +/**
    + * An integration test for MyPlugin.
    + */
    +class MyPluginIT extends EmbeddedExtractionPluginFlits {
    +
    +    @Override
    +    protected ExtractionPlugin pluginToTest() {
    +        // MyPlugin is a class implementing the ExtractionPlugin interface,
    +        // with pluginInfo() and process() methods.
    +        return new MyPlugin();
    +    }
    +
    +    @Override
    +    public Path testPath() {
    +        // Provide the folder containing input files. For examples, see
    +        // https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples.
    +        return srcPath("integration/inputs");
    +    }
    +
    +    @Override
    +    public Path resultPath() {
    +        // Provide the folder containing result files. For examples, see
    +        // https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples.
    +        return srcPath("integration/results");
    +    }
    +
    +    @Override
    +    public boolean regenerate() {
    +        // Returning false means the test will fail if the result files don't
    +        // match the outcome of the=++ test. Returning true means the test create
    +        // new result files .
    +        return false;
    +    }
    +}
    +
    +
    +
    +
    +

    Remote Testing example

    +

    Note that the following example serves the plugin by using ExtractionServer.

    +
    import static nl.minvenj.nfi.flits.util.FlitsUtil.srcPath;
    +
    +import java.nio.file.Path;
    +
    +import org.hansken.plugin.extraction.runtime.grpc.client.ExtractionPluginClient;
    +import org.hansken.plugin.extraction.runtime.grpc.server.ExtractionPluginServer;
    +import org.hansken.plugin.extraction.test.plugins.DataTransformationsPlugin;
    +import org.junit.jupiter.api.AfterAll;
    +import org.junit.jupiter.api.BeforeAll;
    +
    +public class RemoteTransformationPluginFlitsIT extends RemoteExtractionPluginFlits {
    +
    +    private static ExtractionPluginServer _server;
    +    private static ExtractionPluginClient _client;
    +
    +    @BeforeAll
    +    public static void init() throws Exception {
    +        final int port = 8999;
    +
    +        // Serve MyPlugin.
    +        // MyPlugin is a class implementing the ExtractionPlugin interface, with PluginInfo and Process methods.
    +        _server = ExtractionPluginServer.serve(port, MyPlugin::new);
    +
    +        // Create an ExtractionPluginClient
    +        _client = new ExtractionPluginClient("localhost", _server.getListeningPort());
    +    }
    +
    +    @AfterAll
    +    public static void destruct() {
    +        // At the end of the test, make sure the server and client are closed.
    +        if (_server != null) {
    +            _server.close();
    +        }
    +        if (_client != null) {
    +            _client.close();
    +        }
    +    }
    +
    +    @Override
    +    public Path testPath() {
    +        // Provide the folder containing input files. For examples, see https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples.
    +        return srcPath("integration/inputs");
    +    }
    +
    +    @Override
    +    public Path resultPath() {
    +        // Provide the folder containing result files. For examples, see https://git.eminjenv.nl/hanskaton/hansken-extraction-plugin-sdk/examples.
    +        return srcPath("integration/results");
    +    }
    +
    +    @Override
    +    protected ExtractionPluginClient pluginToTest() {
    +        // For Remote testing, the test won't talk directly to the plugin, but to the client.
    +        // The client will use gRPC to communicate with the served plugin.
    +        return _client;
    +    }
    +
    +    @Override
    +    public boolean regenerate() {
    +        // Returning false means the test will fail if the result files don't match the outcome of the test.
    +        // Returning true means the test create new result files.
    +        return false;
    +    }
    +}
    +
    +
    +
    +

    Note

    +

    Note that with a RemoteTransformationPluginFlitsIT it is possible to start a docker image of a plugin and +run remote tests against it using your own testdata. To do this, simply remove all _server code and manually start +your plugin in a docker container. Then run the test against the docker container by setting the correct url and +port, presumably new ExtractionPluginClient("localhost", 8999).

    +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python.html b/0.7.0/dev/python.html new file mode 100644 index 0000000..12f855b --- /dev/null +++ b/0.7.0/dev/python.html @@ -0,0 +1,160 @@ + + + + + + + Python — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/api/hansken_extraction_plugin.api.data_context.html b/0.7.0/dev/python/api/hansken_extraction_plugin.api.data_context.html new file mode 100644 index 0000000..cf347ee --- /dev/null +++ b/0.7.0/dev/python/api/hansken_extraction_plugin.api.data_context.html @@ -0,0 +1,177 @@ + + + + + + + hansken_extraction_plugin.api.data_context — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    hansken_extraction_plugin.api.data_context

    +

    This module contains the definition of the DataContext.

    +

    Classes

    + + + + + + +

    DataContext(data_type, data_size)

    This class contains the data context of a plugin that is processing a trace.

    +
    +
    +class DataContext(data_type: str, data_size: int)[source]
    +

    Bases: object

    +

    This class contains the data context of a plugin that is processing a trace.

    +
    +
    +data_type: str
    +

    the named data type that is being processed

    +
    + +
    +
    +data_size: int
    +

    the size / total length of the data stream that is being processed

    +
    + +
    + +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/api/hansken_extraction_plugin.api.extraction_plugin.html b/0.7.0/dev/python/api/hansken_extraction_plugin.api.extraction_plugin.html new file mode 100644 index 0000000..95cecb7 --- /dev/null +++ b/0.7.0/dev/python/api/hansken_extraction_plugin.api.extraction_plugin.html @@ -0,0 +1,246 @@ + + + + + + + hansken_extraction_plugin.api.extraction_plugin — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    hansken_extraction_plugin.api.extraction_plugin

    +

    This module contains the different types of Extraction Plugins.

    +

    The types of Extraction Plugins differ in their process functions.

    +

    Classes

    + + + + + + + + + + + + + + + +

    BaseExtractionPlugin()

    All Extraction Plugins are derived from this class.

    DeferredExtractionPlugin()

    Extraction Plugin that can be run at a different extraction stage.

    ExtractionPlugin()

    Default extraction plugin, that processes a trace and one of its datastreams.

    MetaExtractionPlugin()

    Extraction Plugin that processes a trace only with its metadata, without processing its data.

    +
    +
    +class BaseExtractionPlugin[source]
    +

    Bases: ABC

    +

    All Extraction Plugins are derived from this class.

    +
    +
    +abstract plugin_info() PluginInfo[source]
    +

    Return information about this extraction plugin.

    +
    + +
    + +
    +
    +class ExtractionPlugin[source]
    +

    Bases: BaseExtractionPlugin

    +

    Default extraction plugin, that processes a trace and one of its datastreams.

    +
    +
    +abstract process(trace: ExtractionTrace, data_context: DataContext)[source]
    +

    Process a given trace.

    +

    This method is called for every trace that is processed by this tool.

    +
    +
    Parameters:
    +
      +
    • trace – Trace that is being processed

    • +
    • data_context – Data data_context describing the data stream that is being processed

    • +
    +
    +
    +
    + +
    + +
    +
    +class MetaExtractionPlugin[source]
    +

    Bases: BaseExtractionPlugin

    +

    Extraction Plugin that processes a trace only with its metadata, without processing its data.

    +
    +
    +abstract process(trace: MetaExtractionTrace)[source]
    +

    Process a given trace.

    +

    This method is called for every trace that is processed by this tool.

    +
    +
    Parameters:
    +

    trace – Trace that is being processed

    +
    +
    +
    + +
    + +
    +
    +class DeferredExtractionPlugin[source]
    +

    Bases: BaseExtractionPlugin

    +

    Extraction Plugin that can be run at a different extraction stage.

    +

    This type of plugin also allows accessing other traces using the searcher.

    +
    +
    +abstract process(trace: ExtractionTrace, data_context: DataContext, searcher: TraceSearcher)[source]
    +

    Process a given trace.

    +

    This method is called for every trace that is processed by this tool.

    +
    +
    Parameters:
    +
      +
    • trace – Trace that is being processed

    • +
    • data_context – Data data_context describing the data stream that is being processed

    • +
    • searcher – TraceSearcher that can be used to obtain more traces

    • +
    +
    +
    +
    + +
    + +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/api/hansken_extraction_plugin.api.extraction_trace.html b/0.7.0/dev/python/api/hansken_extraction_plugin.api.extraction_trace.html new file mode 100644 index 0000000..a0892bb --- /dev/null +++ b/0.7.0/dev/python/api/hansken_extraction_plugin.api.extraction_trace.html @@ -0,0 +1,464 @@ + + + + + + + hansken_extraction_plugin.api.extraction_trace — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    hansken_extraction_plugin.api.extraction_trace

    +

    This module contains the different Trace apis.

    +
    +
    Note that there are a couple of different traces:
      +
    • The ExtractionTrace and MetaExtractionTrace, which are offered to the process function.

    • +
    • ExtractionTraceBuilder, which is a trace that can be built; it does not exist in hansken yet, but it is added after +building.

    • +
    • SearchTrace, which represents an immutable trace which is returned after searching for traces.

    • +
    +
    +
    +

    Classes

    + + + + + + + + + + + + + + + + + + +

    ExtractionTrace()

    Trace offered to be processed.

    ExtractionTraceBuilder()

    ExtractionTrace that can be build.

    MetaExtractionTrace()

    MetaExtractionTraces contain only metadata.

    SearchTrace()

    SearchTraces represent traces returned when searching for traces.

    Trace()

    All trace classes should be able to return values.

    +
    +
    +class ExtractionTraceBuilder[source]
    +

    Bases: ABC

    +

    ExtractionTrace that can be build.

    +

    Represents child traces.

    +
    +
    +abstract update(key_or_updates: Mapping | str | None = None, value: Any | None = None, data: Mapping[str, bytes] | None = None) ExtractionTraceBuilder[source]
    +

    Update or add metadata properties for this .ExtractionTraceBuilder.

    +

    Can be used to update the name of the Trace represented by this builder, +if not already set.

    +
    +
    Parameters:
    +
      +
    • key_or_updates – either a str (the metadata property to be +updated) or a mapping supplying both keys and values to be updated

    • +
    • value – the value to update metadata property key to (used +only when key_or_updates is a str, an exception will be thrown +if key_or_updates is a mapping)

    • +
    • data – a dict mapping data type / stream name to bytes to be +added to the trace

    • +
    +
    +
    Returns:
    +

    this .ExtractionTraceBuilder

    +
    +
    +
    + +
    +
    +abstract add_tracelet(tracelet: Tracelet | str, value: Mapping[str, Any] | None = None) ExtractionTraceBuilder[source]
    +

    Add a .Tracelet to this .ExtractionTraceBuilder.

    +
    +
    Parameters:
    +
      +
    • tracelet – the Tracelet or tracelet type (supplied as a str) to add

    • +
    • value – the tracelet properties to add (only applicable when tracelet is a str)

    • +
    +
    +
    Returns:
    +

    this .ExtractionTraceBuilder

    +
    +
    +
    + +
    +
    +abstract add_transformation(data_type: str, transformation: Transformation) ExtractionTraceBuilder[source]
    +

    Update or add transformations for this .ExtractionTraceBuilder.

    +
    +
    Parameters:
    +
      +
    • data_type – data type of the Transformation

    • +
    • transformation – the Transformation to add

    • +
    +
    +
    Returns:
    +

    this .ExtractionTraceBuilder

    +
    +
    +
    + +
    +
    +abstract child_builder(name: str | None = None) ExtractionTraceBuilder[source]
    +

    Create a new .TraceBuilder to build a child trace to the trace to be represented by this builder.

    +
    +

    Note

    +

    Traces should be created and built in depth first order, +parent before child (pre-order).

    +
    +
    +
    Returns:
    +

    a .TraceBuilder set up to save a new trace as the child +trace of this builder

    +
    +
    +
    + +
    +
    +add_data(stream: str, data: bytes) ExtractionTraceBuilder[source]
    +

    Add data to this trace as a named stream.

    +
    +
    Parameters:
    +
      +
    • stream – name of the data stream to be added

    • +
    • data – data to be attached

    • +
    +
    +
    Returns:
    +

    this .ExtractionTraceBuilder

    +
    +
    +
    + +
    +
    +abstract open(data_type: str | None = None, offset: int = 0, size: int | None = None, mode: Literal['rb', 'wb'] = 'rb') BufferedReader | BufferedWriter[source]
    +

    Open a data stream to read or write data from or to the .ExtractionTrace.

    +
    +
    Parameters:
    +
      +
    • data_type – the data type of the datastream, ‘raw’ by default

    • +
    • offset – byte offset to start the stream on when reading

    • +
    • size – the number of bytes to make available when reading

    • +
    • mode – ‘rb’ for reading, ‘wb’ for writing

    • +
    +
    +
    Returns:
    +

    a file-like object to read or write bytes from the named stream

    +
    +
    +
    + +
    +
    +abstract build() str[source]
    +

    Save the trace being built by this builder to remote.

    +
    +

    Note

    +

    Building more than once will result in an error being raised.

    +
    +
    +
    Returns:
    +

    the new trace’ id

    +
    +
    +
    + +
    + +
    +
    +class Trace[source]
    +

    Bases: ABC

    +

    All trace classes should be able to return values.

    +
    +
    +abstract get(key: str, default: Any | None = None) Any[source]
    +

    Return metadata properties for this .ExtractionTrace.

    +
    +
    Parameters:
    +
      +
    • key – the metadata property to be retrieved

    • +
    • default – value returned if property is not set

    • +
    +
    +
    Returns:
    +

    the value of the requested metadata property

    +
    +
    +
    + +
    + +
    +
    +class SearchTrace[source]
    +

    Bases: Trace

    +

    SearchTraces represent traces returned when searching for traces.

    +
    +
    +abstract open(stream: str = 'raw', offset: int = 0, size: int | None = None) BufferedReader[source]
    +

    Open a data stream of the data that is being processed.

    +
    +
    Parameters:
    +
      +
    • stream – data stream of trace to open. defaults to raw. other examples are html, text, etc.

    • +
    • offset – byte offset to start the stream on

    • +
    • size – the number of bytes to make available

    • +
    +
    +
    Returns:
    +

    a file-like object to read bytes from the named stream

    +
    +
    +
    + +
    + +
    +
    +class MetaExtractionTrace[source]
    +

    Bases: Trace

    +

    MetaExtractionTraces contain only metadata.

    +

    This class represenst traces during the extraction of an extraction plugin without a data stream.

    +
    +
    +abstract update(key_or_updates: Mapping | str | None = None, value: Any | None = None, data: Mapping[str, bytes] | None = None) None[source]
    +

    Update or add metadata properties for this .ExtractionTrace.

    +
    +
    Parameters:
    +
      +
    • key_or_updates – either a str (the metadata property to be +updated) or a mapping supplying both keys and values to be updated

    • +
    • value – the value to update metadata property key to (used +only when key_or_updates is a str, an exception will be thrown +if key_or_updates is a mapping)

    • +
    • data – a dict mapping data type / stream name to bytes to be +added to the trace

    • +
    +
    +
    +
    + +
    +
    +abstract add_tracelet(tracelet: Tracelet | str, value: Mapping[str, Any] | None = None) None[source]
    +

    Add a .Tracelet to this .MetaExtractionTrace.

    +
    +
    Parameters:
    +
      +
    • tracelet – the Tracelet or tracelet type to add

    • +
    • value – the tracelet properties to add (only applicable when tracelet is a tracelet type)

    • +
    +
    +
    +
    + +
    +
    +abstract add_transformation(data_type: str, transformation: Transformation) None[source]
    +

    Update or add transformations for this .ExtractionTraceBuilder.

    +
    +
    Parameters:
    +
      +
    • data_type – data type of the Transformation

    • +
    • transformation – the Transformation to add

    • +
    +
    +
    +
    + +
    +
    +abstract child_builder(name: str | None = None) ExtractionTraceBuilder[source]
    +

    Create a .TraceBuilder to build a trace to be saved as a child of this .Trace.

    +

    A new trace will only be added to the index once explicitly saved (e.g. +through .TraceBuilder.build).

    +
    +

    Note

    +

    Traces should be created and built in depth first order, +parent before child (pre-order).

    +
    +
    +
    Parameters:
    +

    name – the name for the trace being built

    +
    +
    Returns:
    +

    a .TraceBuilder set up to create a child trace of this .MetaExtractionTrace

    +
    +
    +
    + +
    + +
    +
    +class ExtractionTrace[source]
    +

    Bases: MetaExtractionTrace

    +

    Trace offered to be processed.

    +
    +
    +abstract open(data_type: str | None = None, offset: int = 0, size: int | None = None, mode: Literal['rb', 'wb'] = 'rb') BufferedReader | BufferedWriter[source]
    +

    Open a data stream to read or write data from or to the .ExtractionTrace.

    +
    +
    Parameters:
    +
      +
    • data_type – the data type of the datastream, ‘raw’ by default

    • +
    • offset – byte offset to start the stream on when reading

    • +
    • size – the number of bytes to make available when reading

    • +
    • mode – ‘rb’ for reading, ‘wb’ for writing

    • +
    +
    +
    Returns:
    +

    a file-like object to read or write bytes from the named stream

    +
    +
    +
    + +
    + +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/api/hansken_extraction_plugin.api.html b/0.7.0/dev/python/api/hansken_extraction_plugin.api.html new file mode 100644 index 0000000..1d9c34e --- /dev/null +++ b/0.7.0/dev/python/api/hansken_extraction_plugin.api.html @@ -0,0 +1,179 @@ + + + + + + + hansken_extraction_plugin.api — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    hansken_extraction_plugin.api

    +

    This package contains the apis of the different classes used by extraction plugins.

    +

    Implementations can be found in the runtime module.

    +

    Modules

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    hansken_extraction_plugin.api.data_context

    This module contains the definition of the DataContext.

    hansken_extraction_plugin.api.extraction_plugin

    This module contains the different types of Extraction Plugins.

    hansken_extraction_plugin.api.extraction_trace

    This module contains the different Trace apis.

    hansken_extraction_plugin.api.plugin_info

    This module contains all definitions to describe meta data of a plugin, a.k.a.

    hansken_extraction_plugin.api.search_result

    This module contains a representation of a search result.

    hansken_extraction_plugin.api.trace_searcher

    This module contains the definition of a trace searcher.

    hansken_extraction_plugin.api.tracelet

    This module contains the definition of a Tracelet.

    hansken_extraction_plugin.api.transformation

    This module contains the definition of a Transformation.

    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/api/hansken_extraction_plugin.api.plugin_info.html b/0.7.0/dev/python/api/hansken_extraction_plugin.api.plugin_info.html new file mode 100644 index 0000000..8d4b631 --- /dev/null +++ b/0.7.0/dev/python/api/hansken_extraction_plugin.api.plugin_info.html @@ -0,0 +1,326 @@ + + + + + + + hansken_extraction_plugin.api.plugin_info — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    hansken_extraction_plugin.api.plugin_info

    +

    This module contains all definitions to describe meta data of a plugin, a.k.a. PluginInfo.

    +

    Classes

    + + + + + + + + + + + + + + + + + + +

    Author(name, email, organisation)

    The author of an Extraction Plugin.

    MaturityLevel(value)

    This class represents the maturity level of an extraction plugin.

    PluginId(domain, category, name)

    Identifier of a plugin, consisting of domain, category and name.

    PluginInfo(id, version, description, author, ...)

    This information is used by Hansken to identify and run the plugin.

    PluginResources(maximum_cpu, maximum_memory)

    PluginResources contains information about how many resources will be used for a plugin.

    +
    +
    +class Author(name: str, email: str, organisation: str)[source]
    +

    Bases: object

    +

    The author of an Extraction Plugin.

    +

    This information can be retrieved by an end-user from Hansken.

    +
    +
    +name: str
    +
    + +
    +
    +email: str
    +
    + +
    +
    +organisation: str
    +
    + +
    + +
    +
    +class MaturityLevel(value)[source]
    +

    Bases: Enum

    +

    This class represents the maturity level of an extraction plugin.

    +
    +
    +PROOF_OF_CONCEPT = 0
    +
    + +
    +
    +READY_FOR_TEST = 1
    +
    + +
    +
    +PRODUCTION_READY = 2
    +
    + +
    + +
    +
    +class PluginId(domain: str, category: str, name: str)[source]
    +

    Bases: object

    +

    Identifier of a plugin, consisting of domain, category and name. Needs to be unique among all tools/plugins.

    +
    +
    +domain: str
    +
    + +
    +
    +category: str
    +
    + +
    +
    +name: str
    +
    + +
    + +
    +
    +class PluginResources(maximum_cpu: float | None, maximum_memory: int | None)[source]
    +

    Bases: object

    +

    PluginResources contains information about how many resources will be used for a plugin.

    +
    +
    +maximum_cpu: float | None
    +

    CPU resources are measured in cpu units. One cpu is equivalent to 1 vCPU/Core for cloud providers and 1 hyperthread +on bare-metal Intel processors. Also, fractional requests are allowed. A plugin that asks 0.5 CPU uses half as +much CPU as one that asks for 1 CPU.

    +
    + +
    +
    +maximum_memory: int | None
    +

    Max usable memory for a plugin, measured in megabytes.

    +
    + +
    + +
    +
    +class PluginInfo(id: PluginId, version: str, description: str, author: Author, maturity: MaturityLevel, matcher: str, webpage_url: str, license: str | None = None, deferred_iterations: int = 1, resources: PluginResources | None = None)[source]
    +

    Bases: object

    +

    This information is used by Hansken to identify and run the plugin.

    +

    Note that the build_plugin.py build script is used to build a plugin docker image with PluginInfo docker labels.

    +
    +
    +id: PluginId
    +

    a plugin’s unique identifier, see PluginId

    +
    + +
    +
    +version: str
    +

    version of the plugin

    +
    + +
    +
    +description: str
    +

    short description of the functionality of the plugin

    +
    + +
    +
    +author: Author
    +

    the plugin’s author, see Author

    +
    + +
    +
    +maturity: MaturityLevel
    +

    maturity level, see MaturityLevel

    +
    + +
    +
    +matcher: str
    +

    this matcher selects the traces offered to the plugin

    +
    + +
    +
    +webpage_url: str
    +

    plugin url

    +
    + +
    +
    +license: str | None = None
    +

    license of this plugin

    +
    + +
    +
    +deferred_iterations: int = 1
    +

    number of deferred iterations (1 to 20), nly for deferred plugins (optional)

    +
    + +
    +
    +resources: PluginResources | None = None
    +

    resources to be reserved for a plugin (optional)

    +
    + +
    + +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/api/hansken_extraction_plugin.api.search_result.html b/0.7.0/dev/python/api/hansken_extraction_plugin.api.search_result.html new file mode 100644 index 0000000..d339f62 --- /dev/null +++ b/0.7.0/dev/python/api/hansken_extraction_plugin.api.search_result.html @@ -0,0 +1,226 @@ + + + + + + + hansken_extraction_plugin.api.search_result — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    hansken_extraction_plugin.api.search_result

    +

    This module contains a representation of a search result.

    +

    Classes

    + + + + + + +

    SearchResult(*args, **kwds)

    Class representing a stream of traces, returned when performing a search request.

    +
    +
    +class SearchResult(*args, **kwds)[source]
    +

    Bases: ABC, Iterable

    +

    Class representing a stream of traces, returned when performing a search request.

    +

    This result can only be iterated once. Results can be retrieved in three ways:

    +

    Treating the result as an iterable:

    +
    for trace in result:
    +    print(trace.name)
    +
    +
    +

    Calling .take to process one or more batches of traces:

    +
    first_100 = result.take(100)
    +process_batch(first_100)
    +
    +
    +

    Calling .takeone to get a single trace:

    +
    first = result.takeone()
    +second = result.takeone()
    +
    +print(first.name, second.name)
    +
    +
    +
    +
    +abstract total_results() int[source]
    +

    Return the total number of hits.

    +
    +
    Returns:
    +

    Total number of hits

    +
    +
    +
    + +
    +
    +takeone() SearchTrace | None[source]
    +

    Return a single trace, if this stream is not exhausted.

    +
    +
    Returns:
    +

    A searchtrace, or None if no trace is available

    +
    +
    +
    + +
    +
    +take(num: int) List[SearchTrace][source]
    +

    Return a list containing at most num number of traces, or less if they are not available.

    +
    +
    Parameters:
    +

    num – Number of traces to take

    +
    +
    Returns:
    +

    List containing zero or more traces

    +
    +
    +
    + +
    +
    +close()[source]
    +

    Close this SearchResult if no more traces are to be retrieved.

    +

    Required to keep compatibility with hansken.py.

    +
    + +
    + +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/api/hansken_extraction_plugin.api.trace_searcher.html b/0.7.0/dev/python/api/hansken_extraction_plugin.api.trace_searcher.html new file mode 100644 index 0000000..7da9ce5 --- /dev/null +++ b/0.7.0/dev/python/api/hansken_extraction_plugin.api.trace_searcher.html @@ -0,0 +1,182 @@ + + + + + + + hansken_extraction_plugin.api.trace_searcher — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    hansken_extraction_plugin.api.trace_searcher

    +

    This module contains the definition of a trace searcher.

    +

    Classes

    + + + + + + +

    TraceSearcher()

    This class can be used to search for traces, using the search method.

    +
    +
    +class TraceSearcher[source]
    +

    Bases: object

    +

    This class can be used to search for traces, using the search method.

    +
    +
    +abstract search(query: str, count: int) SearchResult[source]
    +

    Search for indexed traces in Hansken using provided query returning at most count results.

    +
    +
    Parameters:
    +
      +
    • query – HQL-query used for searching

    • +
    • count – Maximum number of traces to return

    • +
    +
    +
    Returns:
    +

    SearchResult containing found traces

    +
    +
    +
    + +
    + +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/api/hansken_extraction_plugin.api.tracelet.html b/0.7.0/dev/python/api/hansken_extraction_plugin.api.tracelet.html new file mode 100644 index 0000000..1604078 --- /dev/null +++ b/0.7.0/dev/python/api/hansken_extraction_plugin.api.tracelet.html @@ -0,0 +1,173 @@ + + + + + + + hansken_extraction_plugin.api.tracelet — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    hansken_extraction_plugin.api.tracelet

    +

    This module contains the definition of a Tracelet.

    +

    Classes

    + + + + + + +

    Tracelet(name, value)

    A tracelet contains the values of a single fvt (Few Valued Type).

    +
    +
    +class Tracelet(name: str, value: Mapping[str, Any])[source]
    +

    Bases: object

    +

    A tracelet contains the values of a single fvt (Few Valued Type).

    +

    A few valued type is a trace property type that is a collection of tracelets. A trace can contain multiple few +valued types containing one or more tracelets. For example, the trace.identity` type may look like this:

    +
    {emailAddress: 'interesting@notreally.com'},
    +{firstName: 'piet'},
    +{emailAddress: 'anotheremail@notreally.com'},
    +
    +
    +

    The trace.identity few valued types contains three different tracelets.

    +
    + +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/api/hansken_extraction_plugin.api.transformation.html b/0.7.0/dev/python/api/hansken_extraction_plugin.api.transformation.html new file mode 100644 index 0000000..f2eadf9 --- /dev/null +++ b/0.7.0/dev/python/api/hansken_extraction_plugin.api.transformation.html @@ -0,0 +1,230 @@ + + + + + + + hansken_extraction_plugin.api.transformation — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    hansken_extraction_plugin.api.transformation

    +

    This module contains the definition of a Transformation.

    +

    Classes

    + + + + + + + + + + + + +

    Range(offset, length)

    A Range describes a range of bytes with a offset and length.

    RangedTransformation(ranges)

    A :class:RangedTransformation describes a data transformation consisting of a list of :class:Range.

    Transformation()

    A super class for data transformations.

    +
    +
    +class Transformation[source]
    +

    Bases: ABC

    +

    A super class for data transformations. Currently only :class:RangedTransformation is supported.

    +
    + +
    +
    +class Range(offset: int, length: int)[source]
    +

    Bases: object

    +

    A Range describes a range of bytes with a offset and length.

    +
    +
    +offset: int
    +

    the starting point of the data

    +
    + +
    +
    +length: int
    +

    the size of the data

    +
    + +
    + +
    +
    +class RangedTransformation(ranges: List[Range])[source]
    +

    Bases: Transformation

    +

    A :class:RangedTransformation describes a data transformation consisting of a list of :class:Range.

    +
    +
    +static builder()[source]
    +

    :return a Builder.

    +
    + +
    +
    +class Builder[source]
    +

    Bases: object

    +

    Helper class that implements a transformation builder.

    +
    +
    +add_range(offset: int, length: int) Builder[source]
    +

    Add a range to a ranged transformation by providing the range’s offset and length.

    +

    :param offset the offset of the data transformation +:param length the length of the data transformation +:return: this .RangedTransformation.Builder

    +
    + +
    +
    +build() RangedTransformation[source]
    +

    Return a RangedTransformation.

    +
    +
    Returns:
    +

    a :class:RangedTransformation

    +
    +
    +
    + +
    + +
    + +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/api_changelog.html b/0.7.0/dev/python/api_changelog.html new file mode 100644 index 0000000..472431e --- /dev/null +++ b/0.7.0/dev/python/api_changelog.html @@ -0,0 +1,481 @@ + + + + + + + Python API Changelog — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Python API Changelog

    +

    This document summarizes all important API changes in the Extraction Plugin API. This document only shows changes that +are important to plugin developers. For a full list of changes per version, please refer to the general +changelog.

    +
    +

    0.7.0 +

    +
      +
    • Escaping the / character in matchers is optional. +This simplifies and aims for better HQL and HQL-Lite compatability. +See for more information and examples the HQL-Lite syntax documentation.

      +

      Examples:

      +
        +
      • Old: file.path:\/Users\/*\/AppData -> new: file.path:/Users/*/AppData

      • +
      • Old: file.path:\\/Users\\/*\\/AppData -> new: file.path:/Users/*/AppData

      • +
      • Old: registryEntry.key:\/Software\/Dropbox\/ks*\/Client-p -> new: registryEntry.key:/Software/Dropbox/ks*/Client-p

      • +
      +
    • +
    • Hansken returns file.path properties (outside the scope of matchers) as a String property, +instead of a list of strings. +Example: trace.get('file.path') now returns '/dev/null', this was ['dev', 'null'].

    • +
    • Improved plugin loading when using serve_plugin and build_plugin: +import statements now work for modules (python files) that are located the same directory structure of a plugin.

    • +
    +
    +
    +

    0.6.1

    +
      +
    • The docker image build script build_plugin has been updated to allow for extension of the docker command. +This can be especially handy for specifying a proxy. You should build your plugin container image with the following +command:

      +
      build_plugin PLUGIN_FILE DOCKER_FILE_DIRECTORY [DOCKER_IMAGE_NAME] [DOCKER_ARGS]
      +
      +
      +
      +

      Warning

      +

      Note that the DOCKER_IMAGE_NAME argument no longer requires a -n parameter to be specified.

      +
      +

      For usage read further in packaging.

      +
    • +
    +
    +
    +

    0.6.0

    +
    +

    Warning

    +

    This is an API breaking change. +Upgrading your plugin to this version will require code changes. +Plugins built with previous versions of the SDK from 0.3.0 will still work with Hansken.

    +
    +
    +

    Warning

    +

    It is strongly recommended to upgrade your plugins to this new version because it significantly improves +the start-up time of Hansken. See the migration steps below.

    +
    +

    This release contains both build pipeline changes and API changes. +Please read all changes carefully.

    +
    +

    Build pipeline change

    +
      +
    • Extraction plugin container images are now labeled with PluginInfo. This +allows Hansken to efficiently load extraction plugins. +Migration steps from earlier versions:

      +
        +
      1. Update the SDK version in your setup.py / requirements.txt

      2. +
      3. If you come from a version prior to 0.4.0, or if you use a plugin name +instead of a plugin id in your pluginInfo(), switch to the plugin id style +(read instructions for version 0.4.0)

      4. +
      5. Update your build scripts to build your plugin (Docker) container image. +Be sure to have the Extraction Plugins SDK installed. +Then, you should build your plugin container image with the following command:

        +
        build_plugin PLUGIN_FILE DOCKER_FILE_DIRECTORY -n [DOCKER_IMAGE_NAME]
        +
        +
        +

        For example:

        +
        build_plugin plugin/chatplugin.py . -n extraction-plugins/chatplugin
        +
        +
        +

        This will generate a plugin image:

        +
          +
        • The extraction plugin is added to your local image registry (docker images),

        • +
        • Note that DOCKER_IMAGE_NAME is optional and will default to extraction-plugin/PLUGINID, e.g. +extraction-plugin/nfi.nl/extract/chat/whatsapp,

        • +
        • The image is tagged with two tags: latest, and your plugin version.

        • +
        +
      6. +
      +
    • +
    +
    +
    +

    API changes

    +
      +
    • The field plugin has been removed from PluginInfo.

    • +
    • The field pluginId should now be the first argument of PluginInfo (when using unnamed arguments).

      +

      Old (unnamed arguments):

      +
      def plugin_info(self):
      +    return PluginInfo(self, '1.0.0', 'description', author,
      +                      MaturityLevel.PROOF_OF_CONCEPT, '*, 'https://hansken.org',
      +                      PluginId(...), 'Apache License 2.0')
      +
      +
      +

      New (removed self, and moved PluginId(...) to first argument position):

      +
      def plugin_info(self):
      +    return PluginInfo(PluginId(...), '1.0.0', 'description',
      +                      author, MaturityLevel.PROOF_OF_CONCEPT,
      +                      '*', 'https://hansken.org', 'Apache License 2.0')
      +
      +
      +

      Old (named arguments):

      +
      def plugin_info(self):
      +    return PluginInfo(plugin=self,
      +                      version='1.0.0',
      +                      ...)
      +
      +
      +

      New (removed plugin=self):

      +
      def plugin_info(self):
      +    return PluginInfo(version='1.0.0',
      +                      ...)
      +
      +
      +
    • +
    • Plugin data_context.data_size is now a variable instead of a method:

      +

      Old:

      +
      def process(self, trace: ExtractionTrace, data_context: DataContext):
      +    size = data_context.data_size()
      +
      +
      +

      New:

      +
      def process(self, trace: ExtractionTrace, data_context: DataContext):
      +    size = data_context.data_size
      +
      +
      +
    • +
    • Simplify declaring required runtime resources in a plugin’s info.

      +

      Extraction plugin resources don’t use the builder pattern anymore.

      +

      Old:

      +
      return PluginInfo(
      +    ...,
      +    resources=PluginResources.builder().maximum_cpu(0.5).maximum_memory(1000).build())
      +)
      +
      +
      +

      New:

      +
      # no need for a builder, declare resources by direct instantiation
      +return PluginInfo(
      +    ...,
      +    resources=PluginResources(maximum_cpu=2.0, maximum_memory=2048)
      +)
      +# or, as before, specify just on resource
      +return PluginInfo(
      +    ...,
      +    resources=PluginResources(maximum_memory=4096)
      +)
      +
      +
      +
    • +
    +
    +
    +
    +

    0.5.1

    +
      +
    • Simplify tracelet properties by making the tracelet type prefix optional.

      +
      # using a Tracelet object
      +trace.add_tracelet(Tracelet("prediction", {
      +    "type": "example",
      +    "confidence": 0.8
      +}))
      +# or without a Tracelet object
      +trace.add_tracelet("identity", {"name": "John Doe", "status": "online"})
      +
      +
      +
    • +
    • Enabled manual plugin testing, as described on advanced use of the test framework in Python.

    • +
    +
    +
    +

    0.5.0

    +
      +
    • Support vector data type in trace properties.

      +
      embedding = Vector.from_sequence((width, height))
      +tracelet = Tracelet("prediction", {
      +    "prediction.type": "example-vector",
      +    "prediction.embedding": embedding
      +})
      +trace.add_tracelet(tracelet)
      +
      +
      +
    • +
    +
    +
    +

    0.4.13

    +
      +
    • When writing input search traces for tests, it is no longer required to explicitly set an id property. +These are automatically generated when executing tests.

    • +
    +
    +
    +

    0.4.7

    +
      +
    • More $data matchers are supported in Hansken.py plugin runner. Before this improvement it was only possible to match +on $data.type. Now it is also possible to match for example on $data.mimeType and $data.mimeClass. The $data +matcher should still be at the end of the query as before.

    • +
    +
    +
    +

    0.4.6

    +
      +
    • It is now possible to specify maximum system resources in the PluginInfo. To run a plugin with 0.5 cpu (= 0.5 +vCPU/Core/hyperthread) and 1 gb memory, for example, the following configuration can be added to PluginInfo:

      +
      plugin_info = PluginInfo(...,
      +                         resources=PluginResources.builder().maximum_cpu(0.5).maximum_memory(1000).build())
      +
      +
      +
    • +
    +
    +
    +

    0.4.0

    +
      +
    • Extraction Plugins are now identified with a PluginInfo.PluginId containing a domain, category and name. The +method PluginInfo.name(pluginName) has been replaced by PluginInfo.id(new PluginId(domain, category, name). More +details on the plugin naming conventions can be found at the Plugin naming convention section.

    • +
    • PluginInfo.name() is now deprecated (but will still work for backwards compatibility).

    • +
    • A new license field PluginInfo.license has also been added in this release.

    • +
    • The following example creates a PluginInfo for a plugin with the name TestPlugin, licensed under +the Apache License 2.0 license:

      +
      class TestPlugin(ExtractionPlugin):
      +    def plugin_info(self) -> PluginInfo:
      +        return PluginInfo(self,
      +                          version='1.0.0',
      +                          description='A plugin for testing.',
      +                          author=Author('The Externals', 'tester@holmes.nl', 'NFI'),
      +                          maturity=MaturityLevel.PROOF_OF_CONCEPT,
      +                          webpage_url='https://hansken.org',
      +                          matcher='file.extension=txt',
      +                          id=PluginId(domain='nfi.nl', category='test', name='TestPlugin'),
      +                          license='Apache License 2.0'
      +                          )
      +
      +
      +
    • +
    +
    +
    +

    0.3.0

    +
      +
    • Extraction Plugins can now create new datastreams on a Trace through data transformations. Data transformations +describe how data can be obtained from a source.

      +

      An example case is an extraction plugin that processes an archive file. The plugin creates a child trace per entry in +the archive file. Each child trace will have a datastream that is a transformation that marks the start and length of +the entry in the original archive data. By just describing the data instead of specifying the actual data, a lot of +space is saved.

      +

      Although Hansken supports various transformations, the Extraction Plugins SDK for now only supports ranged data +transformations. Ranged data transformations define data as a list of ranges, each range with an offset and length in +a bytearray.

      +

      The following example sets a new datastream with dataType html on a trace, by setting a ranged data transformation:

      +
      trace.add_transformation('html', RangedTransformation(Range(offset, length)))
      +
      +
      +

      The following example creates a child trace and sets a new datastream with dataType raw on it, by setting a ranged +data transformation with two ranges:

      +
      child = trace.child_builder('new trace')
      +child.add_transformation('raw', RangedTransformation.builder()
      +                                                    .add_range(10, 20)
      +                                                    .add_range(50, 30)
      +                                                    .build())
      +});
      +
      +
      +

      More detailed documentation will follow in an upcoming SDK release.

      +
    • +
    +
    +
    +

    0.2.0

    +
    +

    Warning

    +

    This is an API breaking change. +Plugins created with an earlier version of the extraction plugin +SDK are not compatible with Hansken that uses 0.2.0 or later.

    +
    +
      +
    • Introduced a new extraction plugin type api.extraction_plugin.DeferredExtractioPlugin. +Deferred Extraction plugins can be run at a different extraction stage. +This type of plugin also allows accessing other traces using the searcher.

    • +
    • The class api.extraction_context.ExtractionContext has been renamed to api.data_context.DataContext. +The new name DataContext represents the class contents better. +Plugins have to update matching import statements accordingly. +Plugins should also update the named argument context to data_context of the plugin process() method. +This change has no functional changes.

      +

      Old:

      +
      from hansken_extraction_plugin.api.extraction_context import ExtractionContext
      +
      +def process(self, trace, context):
      +  pass
      +
      +
      +

      New:

      +
      from hansken_extraction_plugin.api.data_context import DataContext
      +
      +def process(self, trace, data_context):
      +  pass
      +
      +
      +
    • +
    • Moved api.author.Author to api.plugin_info.Author, and moved api.maturity_level.MaturityLevel +to api.plugin_info.MaturityLevel +This is a more pythonic way of grouping of classes into modules. This change has no functional side effects.

      +

      Plugins have to update matching import statements accordingly.

      +

      Old:

      +
      from hansken_extraction_plugin.api.author import Author
      +from hansken_extraction_plugin.api.maturity_level import MaturityLevel
      +from hansken_extraction_plugin.api.plugin_info import PluginInfo
      +
      +
      +

      New:

      +
      from hansken_extraction_plugin.api.plugin_info import Author, MaturityLevel, PluginInfo
      +
      +
      +
    • +
    • Removed DataContext.get_first_bytes() from the public API.

    • +
    • Removed api.extraction_trace.validate_update_arguments(..) from the public API. This method is still invoked +implicitly when setting trace properties.

    • +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/debugging.html b/0.7.0/dev/python/debugging.html new file mode 100644 index 0000000..3c21bc3 --- /dev/null +++ b/0.7.0/dev/python/debugging.html @@ -0,0 +1,306 @@ + + + + + + + How to debug an Extraction Plugin — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    How to debug an Extraction Plugin

    +

    Debugging is the art of removing bugs — hopefully quickly.

    +
    +

    Locally

    +

    To debug a plugin locally, it is recommended to start the plugin via the IDE. This has the advantage that breakpoints +can easily be put in the code instead of printing log statements, for example. To start a plugin locally, a piece of +code must be added, see Testing for more information.

    +
    +

    Logging

    +

    The logging of the extraction plugin is displayed in the console.

    +
    +
    +
    +

    Locally with Docker

    +

    Debugging an extraction plugin via docker is a bit trickier. In order to debug in Python, a debugger must be added to +the extraction plugin. There are several debug modules for Python available, but one debug module that works well with +Visual Studio Code is debugpy. This package is developed by Microsoft +specifically for use in Visual Studio Code with Python.

    +
    +

    Note

    +

    debugpy implements the Debug Adapter Protocol (DAP), which is a standardised way for development tools to +communicate with debuggers.

    +
    +

    Using debugpy with Docker containers requires 4 distinct steps:

    +
      +
    1. Install debugpy

    2. +
    3. Configuring debugpy in Python

    4. +
    5. Build a docker image

    6. +
    7. Configuring the connection to the Docker container

    8. +
    9. Setting breakpoints in your code

    10. +
    +
    +

    Install debugpy

    +

    First, add debugpy to your setup.py.

    +
    from setuptools import setup
    +
    +setup(
    +    # ...
    +    install_requires=[
    +        "hansken-extraction-plugin==0.4.7",  # the plugin SDK
    +        "debugpy==1.5.1"
    +    ]
    +)
    +
    +
    +
    +
    +

    Configuring debugpy in Python

    +

    At the beginning of your script, import debugpy, and call debugpy.listen() to start the debug adapter, passing +a (host, port) tuple as the first argument. Use the debugpy.wait_for_client() function to block program execution +until the client is attached.

    +
    import debugpy
    +
    +debugpy.listen(("0.0.0.0", 5678))
    +debugpy.wait_for_client()  # blocks execution until client is attached
    +
    +# your extraction plugin code
    +
    +
    +
    +
    +

    Build a Docker image

    +

    If the Docker image is not built, first build the image as described +here.

    +
    +
    +

    Configuring the connection to the Docker container

    +

    debugpy is now set up to accept connections inside a Docker container. To connect to debugpy in the docker +container, port 5678 must be published. To make a port available to services outside of Docker, use the –publish or -p +flag. This creates a firewall rule which maps a container port to a port on the Docker host to the outside world.

    +

    To run the extraction plugin with the published port the following command can be used:

    +
    docker run -p 5678:5678 your_extraction_plugin_name
    +
    +
    +

    The next step is to configure Visual Studio Code. A launch.json file must be created in order for Visual Studio Code +to connect to the extraction plugin in Docker. This minimal launch.json example below tells the debugger to attach +to localhost on port 5678.

    +
    {
    +  "version": "0.2.0",
    +  "configurations": [
    +    {
    +      "name": "Python: Remote Attach",
    +      "type": "python",
    +      "request": "attach",
    +      "connect": {
    +        "host": "localhost",
    +        "port": 5678
    +      },
    +      "pathMappings": [
    +        {
    +          "localRoot": "${workspaceFolder}",
    +          "remoteRoot": "."
    +        }
    +      ]
    +    }
    +  ]
    +}
    +
    +
    +
    +
    +

    Setting breakpoints in the code

    +

    The last step is to add breakpoints in the code.

    +
    +
    +

    Logging in Docker

    +

    The logging of the extraction plugin is displayed in the console after running the docker run command. In addition, +the logging is also displayed in the Visual Studio Code console while debugging.

    +
    +
    +
    +

    Kubernetes

    +

    In kubernetes it is currently not possible to debug via debugpy because no debug ports are published.

    +
    +

    Logging in Kubernetes

    +

    If there is authorization to the kubernetes cluster, the logging can be viewed with the following command:

    +
    kubectl logs -f hansken-extraction-plugins/your_extraction_plugin_pod
    +
    +
    +
    +
    +
    +

    Debug HQL

    +

    An HQL query can be debugged by running the test framework with the --verbose option enabled. The found HQL matches +will then be displayed in the console. To test a plugin in python with the --verbose option enabled use the following +command:

    +
    test_plugin --standalone plugin/your_plugin.py --regenerate --verbose
    +
    +
    +

    The following output will then be displayed in the console:

    +
    HQL match found for:
    +$data.type=jpg
    +With trace:
    +dataType=jpg
    +types={file, data}
    +properties={data.raw.mimeType=image/jpg, path=/test-input-trace, file.name=image.jpg, name=test-input-trace, id=0}
    +
    +
    +

    If the HQL query contains an error, it will be shown in the generated test results. An example of an invalid query +is $data.mimeType=image/jpg (slash not escaped). This query will produce an error like the one shown below.

    +
    {
    +  "class": "org.hansken.plugin.extraction.hql_lite.lang.ParseException",
    +  "message": "HqlLiteHumanQueryParser: line 1:20 token recognition error at: '/jpg'"
    +}
    +
    +
    +
    +

    Note

    +

    The error is only shown in the generated trace, so to find out the ParseException run the test_plugin +command with the --regenerate option enabled.

    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/getting_started.html b/0.7.0/dev/python/getting_started.html new file mode 100644 index 0000000..f7b419b --- /dev/null +++ b/0.7.0/dev/python/getting_started.html @@ -0,0 +1,325 @@ + + + + + + + Getting started — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Getting started

    +

    Set up a development environment: step by step.

    +

    The following section describes how to set up a fully working development environment for extraction plugins with Python. +This is written for those who are not comfortable setting up a working build environment. +This is optional; advanced users may choose a different development environment setup, and can skip this section completely.

    +

    If you fail to set up a development environment, feel free to ask for help at our Discord channel.

    +
    +

    Install required software on Ubuntu

    +

    In order to be able to develop Hansken Extraction Plugins in Python on Ubuntu, the following build tools need be installed on your system: python, pip, tox, Java, Docker.

    +
      +
    • Python, pip, tox, Java
      +To install, run the following commands in your terminal:

      +
      sudo apt update
      +sudo apt install python3.8 python3-pip tox default-jdk
      +
      +
      +

      You can check whether all versions are installed correctly by running the following commands (and validate the output):

      +
      python --version
      +# should return version 3.8.10 or higher
      +
      +pip3 --version
      +# should return version 20.0.2 or higher
      +
      +tox --version
      +# should return version 3.11.2 or higher
      +
      +java -version
      +# should return version 11.0.4 or higher
      +
      +
      +
    • +
    +

    If the above software is installed, you can continue with “Install Docker”, +or if you don’t want to install Docker, continue with “Install your IDE: Pycharm”.

    +
    +
    +

    Install required software on Windows.

    +

    In order to be able to develop Hansken Extraction Plugins in Python on Windows, follow the next steps:

    +
      +
    • For verification of the installation of each program we will use commands on the command prompt. +This can be opened by clicking the Windows Start button and typing:

      +
      cmd
      +
      +
      +

      Then hit Enter . This will open the command prompt where you can enter the commands given in the following steps.

      +
    • +
    • Python 3.8 or higher & pip

      +

      Download the installer from python.org/Downloads (click the yellow “Download Python” button) +and run it to install Python and pip. +Pip is the standard package manager for Python. +It allows you to install and manage additional packages that are not part of the Python standard library, like the Extraction Plugins SDK.

      +

      Be sure to select the option “Add python to PATH”.

      +

      When the installation is complete, verify the installation by checking the Python and pip versions:

      +
      python --version
      +# should return your downloaded Python version (>3.8.5)
      +
      +pip3 --version
      +# should return 20.2.3 or higher
      +
      +
      +
    • +
    • tox
      +Tox is used to automate and standardize testing in Python. Use Pip to install Tox with this command:

      +
      pip install tox
      +
      +
      +

      Verify that Tox is installed by running:

      +
      tox --version
      +# should return 3.23.0 or higher
      +
      +
      +
    • +
    • Java JDK 11.0.4 (or higher)
      +Java JDK 11.0.4 or higher is needed to run the test framework of the Extraction Plugins SDK. +This enables you to test without actually deploying the plugin in Hansken. +Installing Java on Windows can be done in many different way, but for now we can not recommend one method. +Please have a look at Install the Microsoft Build of OpenJDK for more details.

      +
      java -version
      +# should return 11.0.4 or higher
      +
      +
      +

      N.b. Make sure to set the environment variable in case the installed JDK is not detected by the system. +You can follow the below-mentioned steps to set the environment variable:

      +
        +
      1. Click the Windows start button

      2. +
      3. Type “advanced system settings”

      4. +
      5. Hit enter

      6. +
      7. Now click on Environment Variables, select Path under System Variables section and click on Edit. We need to add the path of installed JDK to system Path.

      8. +
      9. Click on New Button and add the path to installed JDK bin which is C:javajava-11jdk-11.0.4bin in our case.

      10. +
      11. Press OK Button 3 times to close all the windows. This sets the JDK 11 on system environment variables to access the same from the console.

      12. +
      +
    • +
    +

    If the above software is installed, you can continue with “Install Docker”, +or if you don’t want to install Docker, continue with “Install your IDE: Pycharm”.

    +
    +
    +

    Install Docker (Ubuntu, Windows)

    +

    Installing Docker is a bit more complicated. The Docker website describes the installation instructions in detail. +Please follow the instructions on docs.docker.gom/get-docker/ to install docker.

    +
      +
    • To check you have Docker installed correctly, run

      +
      docker --version
      +# should return version 20.10.17 or higher
      +
      +
      +
    • +
    +

    Note: if you run Docker inside a managed network, you might also need to configure proxies and/or certificates. +Please contact your system administrator if you need help with this.

    +
    +
    +

    Set up your IDE: PyCharm

    +

    We recommend that you use an IDE to aid you in your development of Extraction Plugins. PyCharm is a good choice.

    + +
    +
    +

    Download an extraction plugin template (empty plugin)

    +

    You can download an extraction plugin template. +This is an empty plugin, from which you can rapidly start your plugin development.

    + +
    +
    +

    Import the Extraction Plugins Skeleton in PyCharm

    +
      +
    • First unzip the skeleton plugin downloaded from the previous step.

    • +
    • Next, start PyCharm.

    • +
    • When PyCharm starts, choose “Open” and select the folder where you placed the Extraction Plugin Skeleton.

      + +
    • +
    • The following popup will appear, click OK .

      + +
    • +
    • The Extraction Plugins Skeleton is now loaded in PyCharm, which should look as follows:

      + +
    • +
    • Be sure to give the README.md a read when you are done with the Prerequisites.

    • +
    +
    +
    +

    Verify full setup

    +

    To verify that your system has been setup correctly, you can run the test suite in the Extraction Plugin Skeleton:

    +
      +
    • First, press alt-F12 at the same time to open a terminal in PyCharm in the project root folder.

    • +
    • To run the tests of the Skeleton, run this command in the terminal from the root folder:

      +
      tox
      +
      +
      +

      The first time running tox may take a few minutes! Please be patient 😊

      +

      Tox will install all required plugin dependencies, and start your tests. +N.b. The plugin template demonstrates how to build a plugin with tox. +Some plugin developers choose a different tool than tox.

      +
    • +
    • If your system has been set up correctly, the output should end with a summary like this:

      +
      py38: commands succeeded
      +congratulations :)
      +
      +
      +

      This means the setup is finished. You now have everything installed to start coding your own plugin!

      +
    • +
    +
    +
    +

    Next steps

    +

    Now that you have a working environment, you can start doing cool stuff. +Please have a look at the following pages for more information:

    +
      +
    • Packaging: how to package your plugin and use it in Hansken

    • +
    • Run plugins with Hansken.py: run your plugin on a case without uploading the plugin to Hansken, useful for quick prototyping

    • +
    • Testing: how to write tests for your plugin

    • +
    • Debugging: if your plugin isn’t working as expected, you can debug it

    • +
    • Snippets: code snippets to demonstrate common plugin usage patterns

    • +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/hanskenpy.html b/0.7.0/dev/python/hanskenpy.html new file mode 100644 index 0000000..af8e9ab --- /dev/null +++ b/0.7.0/dev/python/hanskenpy.html @@ -0,0 +1,226 @@ + + + + + + + Run plugins with Hansken.py — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Run plugins with Hansken.py

    +

    Hansken.py is a Python client to Hansken’s REST API, developed and maintained by the Netherlands Forensic Institute.

    +

    With Hansken.py, you can run your Python plugin on a project on your Hansken installation that has already been +extracted. This way of running your plugin is useful during your plugin development. It is not required to upload your +plugin to Hansken and start an extraction, making the development cycle faster. However, please note that this is only +useful during the development stage of your plugin, as the execution of your plugin will be much slower compared to +running the plugin during a Hansken extraction.

    +
    +

    Note

    +

    The execution of your plugin will be much slower in Hansken.py compared to running the plugin during a Hansken +extraction.

    +
    +
    +

    How to run python extraction plugins standalone with Hansken.py

    +

    Running python extraction plugins standalone with Hansken.py is easy. It is just one command. This section explains how +to setup this command for your specific environment.

    +
    +

    Create a runner file

    +

    Create a file run_with_hansken.py in the root folder of your plugin. This will aid you in running the plugin with +hansken.py.

    +
    from hansken_extraction_plugin.runtime.extraction_plugin_runner import run_with_hanskenpy
    +
    +from plugin.my_plugin import MyPlugin
    +
    +if __name__ == '__main__':
    +    run_with_hanskenpy(MyPlugin)
    +
    +
    +
    +
    +

    Preparing for the command

    +

    Before you can enter the command that runs your extraction plugin with Hansken.py, you need to find three values:

    +
      +
    • HANSKEN_PROJECT_ID project id on which you want to run your plugin

    • +
    • YOUR_GATEKEEPER_URL the URL to the Hansken gatekeeper

    • +
    • YOUR_KEYSTORE_URL the URL to your keystore

    • +
    +

    The correct values of these variables can be found in the Expert UI. Go to the search-page of your project in the +ExpertUI. Next to the search-bar hit the button “Save query”.

    +hanskenpy_save_query.png +

    A new dialog shows up. At the bottom of this dialog, you will find your gatekeeper and keystore urls as well as the +project id.

    +hanskenpy_gatekeeper_keystore.png +
    +
    +

    Running your plugin with Hansken.py

    +

    Next, open a terminal in the project root folder of your plugin and enter the following command. It will run your +extraction plugin with Hansken.py in Hansken. Replace the three variables with their respective values.

    +
    python3 ./run_with_hanskenpy.py -v -l - HANSKEN_PROJECT_ID --endpoint YOUR_GATEKEEPER_URL --keystore YOUR_KEYSTORE_URL
    +
    +
    +

    If your command runs well, you might be prompted for your username and password. There will be some output (note that +the output may vary depending on your system setup and project content):

    +
    [2021-03-16 12:59:45.344248+0000] INFO: hansken.auth: selected IDP ID (...) with SOAP endpoint (...)
    +[2021-03-16 12:59:45.344450+0000] WARNING: hansken.auth: IDP url known, user+pass auth required but no username supplied
    +username []: testaccount
    +[2021-03-16 12:59:48.423245+0000] INFO: hansken.auth: user acknowledged environment username or supplied custom username: testaccount
    +password for user testaccount:
    +[2021-03-16 12:59:53.799668+0000] INFO: hansken.auth: identity provider url and user+pass provided or known, using Keycloak SAML with Basic auth
    +[2021-03-16 12:59:53.805538+0000] INFO: hansken_extraction_plugin.runtime.extraction_plugin_runner: PluginRunner is running plugin class Plugin
    +[2021-03-16 12:59:53.859299+0000] INFO: hansken.auth: posting SAML request with authorization for user testaccount to IDP endpoint (...)
    +[2021-03-16 12:59:54.240290+0000] INFO: plugin.extraction_plugin: processing trace 54197e67-8135-40c3-93f1-3d73a5552693
    +[2021-03-16 12:59:54.240753+0000] INFO: plugin.extraction_plugin: processing trace OCRimage
    +[2021-03-16 12:59:54.240753+0000] INFO: plugin.extraction_plugin: processing trace (...)
    +
    +
    +

    Note that the arguments -v and -l - are passed to enable logging. To find out what other options can be passed to +this command, please have a look at the hansken.py documentation, or simply run the following command:

    +
    python3 ./run_with_hanskenpy.py --help
    +
    +
    +
    +
    +
    +

    Compatibility

    +

    At this moment, running Extraction Plugins with Hansken.py has a few limitations. These are:

    +
      +
    • When writing an Extraction Plugin for use with Hansken.py, the matcher must contain exactly one “$data.property = +value” expression.

    • +
    • Data transformations are currently not supported by Hansken.py.

    • +
    • Tracelets are not yet supported by the SDK in use with Hansken.py.

    • +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/packaging.html b/0.7.0/dev/python/packaging.html new file mode 100644 index 0000000..1d898cf --- /dev/null +++ b/0.7.0/dev/python/packaging.html @@ -0,0 +1,171 @@ + + + + + + + Packaging — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Packaging

    +

    Extraction plugins are packaged as OCI images (also known as Docker images). +The OCI images are labeled with the PluginInfo. +To automate packaging of a Python plugin and labeling the OCI image, +the Extraction Plugin SDK comes with a utility application build_plugin.

    +

    Make sure that the Extraction Plugins SDK is installed as well as Docker.

    +

    Then, build your plugin container image using the following command:

    +
    build_plugin PLUGIN_FILE DOCKER_FILE_DIRECTORY [DOCKER_IMAGE_NAME] [DOCKER_ARGS]
    +
    +
    +

    For example:

    +
    build_plugin chatplugin.py . chatplugin --build-arg http_proxy="$http_proxy" --build-arg https_proxy="$https_proxy"
    +
    +
    +

    This will generate a plugin image:

    +
      +
    • The extraction plugin is added to your local image registry (docker images),

    • +
    • Note that the variables $http_proxy and $https_proxy are put in quotes, this is needed in case they contain +spaces,

    • +
    • The image is tagged with two tags: latest, and your plugin version.

    • +
    +

    Arguments:

    +
      +
    • PLUGIN_FILE: Path to the python file of the plugin.

    • +
    • DOCKER_FILE_DIRECTORY: Path to the directory containing the Dockerfile of the plugin.

    • +
    • (Optional) [DOCKER_IMAGE_NAME]: Name of the docker image without tag. Note that docker image names cannot start with +a period or dash. If it starts with a dash, it will be interpreted as an additional docker argument (see +DOCKER_ARGS). If no name is given the name defaults to extraction-plugin/PLUGINID, e.g. +extraction-plugin/nfi.nl/extract/chat/whatsapp.

    • +
    • (Optional) [DOCKER_ARGS]: Additional arguments for the docker command, which can be as many arguments as you like.

    • +
    +

    To verify that the image has been built, type the following command to view all local images:

    +
    docker images
    +
    +
    +

    Once your plugin is packaged, it can be published or ‘uploaded’ to Hansken. +See “Upload the plugin to Hansken” for instructions.

    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/prerequisites.html b/0.7.0/dev/python/prerequisites.html new file mode 100644 index 0000000..066ffb0 --- /dev/null +++ b/0.7.0/dev/python/prerequisites.html @@ -0,0 +1,142 @@ + + + + + + + Prerequisites — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Prerequisites

    +

    All required project dependencies to build extraction plugins are published on the public PyPI.

    +

    Required:

    +
      +
    • Python 3.8 or higher

    • +
    • Java 11 (for running the test-framework, which is implemented in Java)

    • +
    • Docker (for packaging and deploying extraction plugins in containers, can be omitted if you have an external build pipeline that provides Docker)

    • +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/snippets.html b/0.7.0/dev/python/snippets.html new file mode 100644 index 0000000..63ea8f5 --- /dev/null +++ b/0.7.0/dev/python/snippets.html @@ -0,0 +1,358 @@ + + + + + + + Python code snippets — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Python code snippets

    +
    +

    Adding properties to a trace

    +

    Use update +to add trace types and their properties to an +ExtractionTrace. +Example:

    +
    def process(self, trace, data_context):
    +    # get the name of the file
    +    file_name = trace.get('file.name')
    +    # set the chat application property on the trace
    +    trace.update('chatConversation.application', f'DemoApp {file_name}')
    +
    +
    +

    All types and properties that can be set are defined in the Hansken trace model.

    +
    +

    Date properties

    +

    When adding a property which holds a value of data-type Date, always define timezone as being UTC. Example:

    +
    def process(self, trace, data_context):
    +    trace.update('file.modifiedOn',
    +                 datetime.fromtimestamp(1630510809, tz=timezone.utc))
    +
    +
    +
    +
    +

    Category for extra properties

    +

    If the information, which must be added as a property, does not match any of the existing properties of Hansken trace +model, use the category “misc” (miscellaneous). When part of the category “misc”, any name can be given to a property. +The values of miscellaneous properties are expected to be of data-type string. Example:

    +
    def process(self, trace, data_context):
    +    trace.update({
    +        'file.misc.notes': 'Some additional notes about the file trace.',
    +        'file.misc.anyName': 'Even more notes.'
    +    })
    +
    +
    +
    +
    +

    Adding tracelets

    +

    In the following Python example, a “prediction” tracelet is added to a trace. The tracelet consists +of a list of four properties, namely “class”, “confidence”, “modelName” and “modelVersion”.

    +
    trace.add_tracelet(Tracelet('prediction', {'class': 'telephone',
    +                                           'confidence': 0.8,
    +                                           'modelName': 'yolo',
    +                                           'modelVersion': '2.0'}))
    +
    +
    +
    +
    +
    +

    Adding child traces to a trace

    +

    Adding child traces to the trace can be done by creating a builder with +child_builder. +Example:

    +
    def process(self, trace, data_context):
    +    child_builder = trace.child_builder('childTrace-1')
    +    child_builder.update({
    +        'chatMessage.application': 'DemoApp',
    +        'chatMessage.from': 'Ann',
    +        'chatMessage.to': ['Mark'],
    +        # list, because there can be multiple receivers
    +        'chatMessage.message': 'Hello, are you there?',
    +    }).build()
    +    grandchild_builder = child_builder.child_builder('grandchild')
    +    grandchild_builder.update(data={'byte': b'some bytes'})
    +    grandchild_builder.build()
    +
    +
    +

    This adds a single child trace with name childTrace-1 with four properties and a grandchild trace with name +grandchild and a byte data stream.

    +
    +
    +

    Adding data to a trace

    +

    Traces can have data attached to them. See Data streams for more information. +The following two snippets demonstrate how to add data to a trace.

    +

    It is currently not possible to verify that a specific data stream is already set or not.

    +
    +

    Data Transformations

    +

    The most efficient way to add data to a trace is using data transformations. +See Data Transformations for more details.

    +

    The following example sets a new datastream with dataType html on a trace, by setting a ranged data transformation:

    +
    trace.add_transformation('html', RangedTransformation(Range(offset, length)))
    +
    +
    +

    The following example creates a child trace and sets a new datastream with dataType raw on it, by setting a ranged +data transformation with two ranges:

    +
    child = trace.child_builder('new trace')
    +child.add_transformation('raw', RangedTransformation.builder()
    +                         .add_range(10, 20)
    +                         .add_range(50, 30)
    +                         .build())
    +});
    +
    +
    +
    +
    +

    Blobs

    +

    It is not always possible to create a transformation for the data that has to be +added to a trace. For example, if the data is a result of a computation, and not +a direct subset of another data stream..

    +

    The following snippet shows how to create a new data stream of dataType raw on a trace from a blob stored in bytes:

    +
    data = {'raw': b'...'}
    +trace.update(data=data);
    +
    +
    +
    +

    Streaming data

    +
    +

    Warning

    +

    Streaming data does not work with the Hansken.py runner because Hansken.py does not support it. It does +work when running your plugin in Hansken and in the test framework.

    +
    +

    When dealing with large quantities of data, it is possible to keep the memory usage +of the plugin within manageable limits by streaming the data from the plugin to Hansken in smaller chunks. +To do this, use the with trace.open(data_type=..., mode='wb') syntax. Here are some examples:

    +

    Stream strings to raw (default) datastream:

    +
    with trace.open(mode='wb') as writer:
    +    writer.write(b'a string')
    +    writer.write(bytes(another_string, 'utf-8'))
    +
    +
    +

    Stream a BufferedReader object to a text datastream:

    +
    with trace.open(data_type='text', mode='wb') as output, open('input.text', 'rb') as in_file:
    +    output.write(in_file)
    +
    +
    +
    +
    +
    +
    +

    Specifying system resources

    +

    It is possible to specify maximum system resources in the PluginInfo. To run a plugin with 0.5 cpu (= 0.5 +vCPU/Core/hyperthread) and 1 gb memory, for example, the following configuration can be added to PluginInfo:

    +
    plugin_info = PluginInfo(...,
    +                         resources=PluginResources(maximum_cpu=0.5, maximum_memory=1000))
    +
    +
    +
    +
    +

    Deferred Plugins

    +

    Implementing a deferred extraction plugin requires inheriting the +DeferredExtractionPlugin +base class.

    +
    class DeferredPlugin(DeferredExtractionPlugin):
    +    def process(self, trace, context, searcher):
    +
    +
    +

    This allows accessing a third TraceSearcher +parameter in the process function. This can be used to search for traces:

    +
    with searcher.search('file.extension:html', 10) as searchresult:
    +    for trace in searchresult:
    +        log.debug(f'extension {trace.get("file.extension")}')
    +
    +
    +

    The search method accepts two arguments; a HQL query and the maximum number of traces the return. The search method +accepts an HQL query and a count, which represents the maximum number of traces to return.

    +

    It may be useful to specifically search for traces from the image being extracted. Add "image:" + trace.get("image") +to your query. The query of the provided example could be extended like +this: "file.extension:html AND image:" + trace.get("image").

    +

    The returned SearchResult +should be closed, for example by using with. The resulting search result is an iterable, which will be exhausted when +no more traces are available. The search result allows taking one or more traces by calling :py: +meth:take <hansken_extraction_plugin.api.search_result.SearchResult.take> or +takeone.

    +
    +
    +

    Logging

    +

    We use Logbook to log messages in Python. Logbook is a logging system for Python that replaces the standard library’s +logging module.

    +

    To enable logging in your plugin, add the following to the top of your plugin code:

    +
    from logbook import Logger
    +
    +log = Logger(__name__)
    +
    +
    +

    From there on the logging is pretty straight forward:

    +
    log.info(f'Logging a variable: {my_variable}')
    +
    +
    +

    The default log level is WARNING. You can use the -v (or -vv or -vvv) option of serve_plugin.py to increase the +log level. This is typically done in the plugin Dockerfile.

    +
    +

    Warning

    +

    Be careful with logging sensitive information.

    +
    +
    +

    Note

    +

    Contact your Hansken administrator for more information on where to find logs for your Hansken environment.

    +
    +
    +
    +

    [EXPERIMENTAL FEATURE] Adding previews to a trace

    +
    +

    Warning

    +

    This is an experimental feature, which might change or get removed in future releases.

    +
    +

    Use update +to add previews to an +ExtractionTrace. +Example:

    +
    def process(self, trace, data_context):
    +    # set the preview data for the image/png MIME-type
    +    trace.update('preview.image/png', b'\x00\xff')
    +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/python/testing.html b/0.7.0/dev/python/testing.html new file mode 100644 index 0000000..dc6344e --- /dev/null +++ b/0.7.0/dev/python/testing.html @@ -0,0 +1,232 @@ + + + + + + + Advanced use of the Test Framework in Python — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Advanced use of the Test Framework in Python

    +

    This section assumes you use the same setup as is used in +the Extraction Plugin Examples.

    +

    By default, the build scripts as described in the Getting Started section will automatically run +tests. The appropriate commands have been added to the tox.ini directly. This section gives a little more detail on the +test commands and options.

    +

    One can simply create unit tests for a plugin directly. However, we also provide a test-framework for testing them over +gRPC. The test-framework serves a running instance of a Python plugin, and feeds it input files and compares the results +against an expected result set.

    +

    Note that the test-framework is implemented in Java, hence the Java 11 requirement. A jar file is included in the Python +SDK which is called from a Python wrapper.

    +
    +

    Regenerate expected test results

    +

    The build and test scripts run some integration tests. To update the expected test outcome, the following command can be +used:

    +
    tox -e regenerate
    +
    +
    +
    +
    +

    Standalone testing

    +

    The test runner is a script called test_plugin which is available in the SDK.

    +

    To get started, cd into the directory of the plugin you want to test and run:

    +
    test_plugin --standalone plugin/chat_plugin.py
    +
    +
    +

    Note that the argument provided to the option --standalone must be the relative path to the plugin py file which is +to be tested. This test accepts input files from the directory testdata/input and compares the results to the result +files in found in testdata/results. Use the optional argument --regenerate to regenerate the expected results for +the test when needed.

    +

    This standalone test is also used by the tox.ini file to validate the plugin. Simply calling tox should be enough to +install all dependencies and run the test.

    +
    +
    +

    Testing with a Docker image

    +

    If there is a docker image available for the plugin you can also test it by executing:

    +
    test_plugin --docker extraction-plugin-examples-my-plugin
    +
    +
    +

    Replace the ‘extraction-plugin-examples-chat’ with the docker image you want to test. Run the following command to see +which docker images are available:

    +
    docker images
    +
    +
    +
    +
    +

    Manual testing

    +

    The third option for testing is a manually started plugin. Start the plugin service in a terminal by executing:

    +
    serve_plugin -vvv plugin/my_plugin.py
    +
    +
    +

    This will spin up the chat plugin at port 8999. Here also the argument must be a path to the plugin’s .py file. In +another terminal window, run the test with:

    +
    test_plugin --manual localhost 8999
    +
    +
    +
    +
    +

    Tip: Start tests in your IDE

    +

    To start the extraction plugin from code, create a __main__ method which calls the _test_validate_standalone +method of the test framework (see the example below). This method causes the extraction plugin to be started and +supplied with data by the FLITS test framework. In this way the test can be started from the IDE, which has the +advantage that it is easier to debug.

    +
    from hansken_extraction_plugin.test_framework.test_plugin import _test_validate_standalone
    +from hansken_extraction_plugin.api.extraction_plugin import ExtractionPlugin
    +
    +
    +class PluginToTest(ExtractionPlugin):
    +
    +    def plugin_info(self):
    +        # return plugin info
    +        pass
    +
    +    def process(self, trace, data_context):
    +        # process the data/trace here
    +        pass
    +
    +
    +if __name__ == '__main__':
    +    _test_validate_standalone(PluginToTest, 'testdata/input', 'testdata/result', False)
    +
    +
    +
    +
    +

    Help

    +

    Run the following for an overview of all the available options in the test script:

    +
    test_plugin --help
    +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/dev/spec.html b/0.7.0/dev/spec.html new file mode 100644 index 0000000..73c7e1b --- /dev/null +++ b/0.7.0/dev/spec.html @@ -0,0 +1,184 @@ + + + + + + + Extraction Plugin specifications — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Extraction Plugin specifications

    +
    +

    Note

    +

    If you use the Java or Python extraction plugin SDK, you don’t have +to worry about these specifications. The Java and Python SDKs makes +sure your plugin is compiled and packaged conform to the extraction +plugin specifications.

    +
    +

    This page describes the specifications that define an extraction plugin. +The spec contains two major parts: a plugin protocol, and the plugin packaging +method.

    +

    This specification applies to plugins that are not embedded within Hansken, +but to plugins that developed and distributed outside the scope of the Hansken +platform development.

    +
    +

    Plugin protocol

    +

    An extraction plugin is a process that implements a GRPC +service ExtractionPluginService. The service defines a protocol that is +used to allow communication between Hansken and an extraction plugin. The +GRPC and protocol definitions can be found in the extraction plugin source +code, under the folder grpc.

    +
    +

    Note

    +

    The source code of the Extraction Plugin is currently not available +outside the scope of the Hansken core development teams. If you are +interested in the GRPC definitions, please Contact the +Hansken development team.

    +
    +
    +
    +

    Packaging

    +

    An extraction plugin is packaged as a container image – conform the open +container initiative image spec. +An extraction plugin can be

    +

    The ENTRYPOINT of the container image should be a process that starts a GRPC +server that implements the plugin protocol. The GRPC protocol should run on +port 8999.

    +

    The container image should be labeled with the plugin info. +The plugin info returned by the plugin plugin-info call and container labels +are required to match. If not, Hansken will not accept your plugin during +extractions - as it is unsure if the intended plugin is processing traces.

    +

    The labels that are expected are:

    +
      +
    • org.hansken.plugin-id (conform Plugin naming convention)

    • +
    • org.hansken.plugin-version

    • +
    • org.hansken.plugin-api-version

    • +
    • org.hansken.plugin-description

    • +
    • org.hansken.plugin-webpage

    • +
    • org.hansken.plugin-deferred-iterations (optional, only has a meaning for deferred extraction plugins)

    • +
    • org.hansken.plugin-matcher (see HQL-Lite)

    • +
    • org.hansken.plugin-license

    • +
    • org.hansken.plugin-author-name

    • +
    • org.hansken.plugin-author-organisation

    • +
    • org.hansken.plugin-author-email

    • +
    • org.hansken.plugin-resource-max_cpu (in milicpu, optional)

    • +
    • org.hansken.plugin-resource-max_mem (in mbs, optional)

    • +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/genindex.html b/0.7.0/genindex.html new file mode 100644 index 0000000..79610e1 --- /dev/null +++ b/0.7.0/genindex.html @@ -0,0 +1,524 @@ + + + + + + Index — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    +
      +
    • + +
    • +
    • +
    +
    +
    +
    +
    + + +

    Index

    + +
    + A + | B + | C + | D + | E + | G + | H + | I + | L + | M + | N + | O + | P + | R + | S + | T + | U + | V + | W + +
    +

    A

    + + + +
    + +

    B

    + + + +
    + +

    C

    + + + +
    + +

    D

    + + + +
    + +

    E

    + + + +
    + +

    G

    + + +
    + +

    H

    + + + +
      +
    • + hansken_extraction_plugin.api + +
    • +
    • + hansken_extraction_plugin.api.data_context + +
    • +
    • + hansken_extraction_plugin.api.extraction_plugin + +
    • +
    • + hansken_extraction_plugin.api.extraction_trace + +
    • +
    • + hansken_extraction_plugin.api.plugin_info + +
    • +
      +
    • + hansken_extraction_plugin.api.search_result + +
    • +
    • + hansken_extraction_plugin.api.trace_searcher + +
    • +
    • + hansken_extraction_plugin.api.tracelet + +
    • +
    • + hansken_extraction_plugin.api.transformation + +
    • +
    + +

    I

    + + +
    + +

    L

    + + + +
    + +

    M

    + + +
    + +

    N

    + + +
    + +

    O

    + + + +
    + +

    P

    + + + +
    + +

    R

    + + + +
    + +

    S

    + + + +
    + +

    T

    + + + +
    + +

    U

    + + +
    + +

    V

    + + +
    + +

    W

    + + +
    + + + +
    +
    +
    + +
    + +
    +

    © Copyright 2020-2023 Netherlands Forensic Institute.

    +
    + + Built with Sphinx using a + theme + provided by Read the Docs. + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/index.html b/0.7.0/index.html new file mode 100644 index 0000000..988a767 --- /dev/null +++ b/0.7.0/index.html @@ -0,0 +1,167 @@ + + + + + + + Hansken extraction plugin SDK documentation for plugin developers — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    +
      +
    • + +
    • + View page source +
    • +
    +
    +
    +
    +
    + +
    +

    Hansken extraction plugin SDK documentation for plugin developers

    +_images/cartoon.png + +
    +

    Welcome

    +

    Welcome to the Hansken extraction plugin SDK documentation for plugin developers. +This documentation describes the Hansken extraction plugin Software Development Kit (SDK). +If you are new here, you can start by reading the introduction.

    +
    +

    Attention

    +

    Hansken extraction plugins is a technology preview. +Please don’t consider the SDK and integration in Hansken to be fully stable.

    +
    + +

    +Cartoon by @jorgb, all rights reserved +

    +
    +
    + + +
    +
    +
    + +
    + +
    +

    © Copyright 2020-2023 Netherlands Forensic Institute.

    +
    + + Built with Sphinx using a + theme + provided by Read the Docs. + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/objects.inv b/0.7.0/objects.inv new file mode 100644 index 0000000..c5e10e0 Binary files /dev/null and b/0.7.0/objects.inv differ diff --git a/0.7.0/py-modindex.html b/0.7.0/py-modindex.html new file mode 100644 index 0000000..1cd69e5 --- /dev/null +++ b/0.7.0/py-modindex.html @@ -0,0 +1,179 @@ + + + + + + Python Module Index — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    +
      +
    • + +
    • +
    • +
    +
    +
    +
    +
    + + +

    Python Module Index

    + +
    + h +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
    + h
    + hansken_extraction_plugin +
        + hansken_extraction_plugin.api +
        + hansken_extraction_plugin.api.data_context +
        + hansken_extraction_plugin.api.extraction_plugin +
        + hansken_extraction_plugin.api.extraction_trace +
        + hansken_extraction_plugin.api.plugin_info +
        + hansken_extraction_plugin.api.search_result +
        + hansken_extraction_plugin.api.trace_searcher +
        + hansken_extraction_plugin.api.tracelet +
        + hansken_extraction_plugin.api.transformation +
    + + +
    +
    +
    + +
    + +
    +

    © Copyright 2020-2023 Netherlands Forensic Institute.

    +
    + + Built with Sphinx using a + theme + provided by Read the Docs. + + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/0.7.0/search.html b/0.7.0/search.html new file mode 100644 index 0000000..e6c253f --- /dev/null +++ b/0.7.0/search.html @@ -0,0 +1,134 @@ + + + + + + Search — Hansken Extraction Plugins for plugin developers 0.7.0 + documentation + + + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    +
      +
    • + +
    • +
    • +
    +
    +
    +
    +
    + + + + +
    + +
    + +
    +
    +
    + +
    + +
    +

    © Copyright 2020-2023 Netherlands Forensic Institute.

    +
    + + Built with Sphinx using a + theme + provided by Read the Docs. + + +
    +
    +
    +
    +
    + + + + + + + + + \ No newline at end of file diff --git a/0.7.0/searchindex.js b/0.7.0/searchindex.js new file mode 100644 index 0000000..bd376d8 --- /dev/null +++ b/0.7.0/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"docnames": ["changes", "contact", "dev/concepts", "dev/concepts/all_in_one_debugging", "dev/concepts/anatomy_of_a_plugin", "dev/concepts/data_transformations", "dev/concepts/extraction_plugins", "dev/concepts/hql_lite", "dev/concepts/isolation", "dev/concepts/kubernetes_autoscaling", "dev/concepts/plugin_naming_convention", "dev/concepts/plugin_types", "dev/concepts/test_framework", "dev/concepts/traces", "dev/examples", "dev/faq", "dev/introduction", "dev/java", "dev/java/api_changelog", "dev/java/debugging", "dev/java/javadoc", "dev/java/packaging", "dev/java/prerequisites", "dev/java/snippets", "dev/java/testing", "dev/python", "dev/python/api/hansken_extraction_plugin.api", "dev/python/api/hansken_extraction_plugin.api.data_context", "dev/python/api/hansken_extraction_plugin.api.extraction_plugin", "dev/python/api/hansken_extraction_plugin.api.extraction_trace", "dev/python/api/hansken_extraction_plugin.api.plugin_info", "dev/python/api/hansken_extraction_plugin.api.search_result", "dev/python/api/hansken_extraction_plugin.api.trace_searcher", "dev/python/api/hansken_extraction_plugin.api.tracelet", "dev/python/api/hansken_extraction_plugin.api.transformation", "dev/python/api_changelog", "dev/python/debugging", "dev/python/getting_started", "dev/python/hanskenpy", "dev/python/packaging", "dev/python/prerequisites", "dev/python/snippets", "dev/python/testing", "dev/spec", "index"], "filenames": ["changes.rst", "contact.md", "dev/concepts.rst", "dev/concepts/all_in_one_debugging.md", "dev/concepts/anatomy_of_a_plugin.md", "dev/concepts/data_transformations.md", "dev/concepts/extraction_plugins.md", "dev/concepts/hql_lite.md", "dev/concepts/isolation.md", "dev/concepts/kubernetes_autoscaling.md", "dev/concepts/plugin_naming_convention.md", "dev/concepts/plugin_types.md", "dev/concepts/test_framework.md", "dev/concepts/traces.md", "dev/examples.md", "dev/faq.md", "dev/introduction.md", "dev/java.rst", "dev/java/api_changelog.md", "dev/java/debugging.md", "dev/java/javadoc.md", "dev/java/packaging.md", "dev/java/prerequisites.md", "dev/java/snippets.md", "dev/java/testing.md", "dev/python.rst", "dev/python/api/hansken_extraction_plugin.api.rst", "dev/python/api/hansken_extraction_plugin.api.data_context.rst", "dev/python/api/hansken_extraction_plugin.api.extraction_plugin.rst", "dev/python/api/hansken_extraction_plugin.api.extraction_trace.rst", "dev/python/api/hansken_extraction_plugin.api.plugin_info.rst", "dev/python/api/hansken_extraction_plugin.api.search_result.rst", "dev/python/api/hansken_extraction_plugin.api.trace_searcher.rst", "dev/python/api/hansken_extraction_plugin.api.tracelet.rst", "dev/python/api/hansken_extraction_plugin.api.transformation.rst", "dev/python/api_changelog.md", "dev/python/debugging.md", "dev/python/getting_started.md", "dev/python/hanskenpy.md", "dev/python/packaging.md", "dev/python/prerequisites.md", "dev/python/snippets.md", "dev/python/testing.md", "dev/spec.md", "index.md"], "titles": ["Changelog", "Contact", "General concepts", "Debugging locally with Hansken All in One (AIO)", "Anatomy of a plugin", "Data Transformations", "Hansken Extraction Plugins", "HQL-Lite", "Plugin isolation", "Kubernetes, Autoscaling, Resourcemanagement", "Plugin naming convention", "Extraction plugin types", "Test framework", "Traces & Trace model", "Examples", "Frequently Asked Questions", "Introduction", "Java", "Java API Changelog", "How to debug an Extraction Plugin", "Javadoc", "Packaging", "Prerequisites", "Java code snippets", "Using the Test Framework in Java", "Python", "hansken_extraction_plugin.api", "hansken_extraction_plugin.api.data_context", "hansken_extraction_plugin.api.extraction_plugin", "hansken_extraction_plugin.api.extraction_trace", "hansken_extraction_plugin.api.plugin_info", "hansken_extraction_plugin.api.search_result", "hansken_extraction_plugin.api.trace_searcher", "hansken_extraction_plugin.api.tracelet", "hansken_extraction_plugin.api.transformation", "Python API Changelog", "How to debug an Extraction Plugin", "Getting started", "Run plugins with Hansken.py", "Packaging", "Prerequisites", "Python code snippets", "Advanced use of the Test Framework in Python", "Extraction Plugin specifications", "Hansken extraction plugin SDK documentation for plugin developers"], "terms": {"The": [0, 5, 6, 7, 8, 9, 10, 11, 13, 15, 16, 18, 19, 21, 22, 23, 24, 28, 29, 30, 33, 35, 36, 37, 38, 39, 41, 42, 43], "follow": [0, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 16, 18, 19, 21, 22, 23, 24, 35, 36, 37, 38, 39, 41, 42], "page": [0, 4, 8, 15, 19, 23, 37, 38, 43], "list": [0, 3, 4, 5, 7, 10, 12, 13, 18, 23, 31, 34, 35, 41], "all": [0, 2, 4, 6, 7, 11, 12, 13, 15, 16, 18, 22, 23, 24, 28, 29, 30, 35, 37, 39, 40, 41, 42, 44], "technic": 0, "chang": [0, 12, 18, 23, 41], "extract": [0, 1, 2, 3, 5, 7, 8, 9, 10, 14, 16, 17, 18, 20, 21, 22, 24, 25, 26, 28, 29, 30, 35, 39, 40, 41, 42], "plugin": [0, 1, 2, 3, 5, 9, 14, 17, 18, 20, 21, 22, 24, 25, 26, 27, 28, 29, 30, 35, 39, 40, 42], "sdk": [0, 1, 5, 9, 11, 12, 18, 20, 23, 24, 35, 36, 37, 38, 39, 42, 43], "program": [0, 4, 12, 16, 36, 37], "languag": [0, 7, 12, 16], "specif": [0, 10, 11, 12, 13, 23, 36, 38, 41, 44], "api": [0, 13, 15, 16, 17, 20, 23, 24, 38, 41, 42, 43, 44], "ar": [0, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 16, 18, 19, 21, 22, 23, 24, 28, 29, 30, 31, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44], "describ": [0, 4, 5, 8, 10, 12, 13, 18, 19, 21, 23, 28, 30, 34, 35, 36, 37, 42, 43, 44], "more": [0, 4, 6, 7, 9, 10, 13, 15, 18, 23, 24, 28, 29, 31, 33, 35, 36, 37, 41, 42], "detail": [0, 4, 10, 13, 15, 16, 18, 23, 35, 37, 41, 42], "These": [0, 6, 7, 10, 11, 12, 13, 18, 23, 35, 38], "new": [0, 5, 7, 11, 13, 16, 18, 19, 23, 24, 29, 35, 37, 38, 41, 44], "function": [0, 4, 7, 11, 18, 19, 23, 28, 29, 30, 35, 36, 41], "how": [0, 5, 6, 9, 13, 17, 18, 23, 25, 30, 35, 37, 41, 44], "updat": [0, 18, 23, 29, 35, 37, 41, 42], "your": [0, 1, 3, 6, 7, 11, 18, 19, 21, 23, 24, 35, 36, 39, 41, 43], "when": [0, 3, 6, 11, 12, 13, 18, 23, 29, 31, 35, 37, 38, 41, 42], "order": [0, 12, 16, 29, 36, 37], "For": [0, 4, 6, 7, 9, 12, 13, 15, 18, 19, 21, 22, 23, 24, 33, 35, 37, 39, 41, 44], "see": [0, 1, 4, 6, 7, 12, 13, 15, 18, 21, 23, 24, 30, 35, 36, 39, 41, 42, 43, 44], "java": [0, 5, 6, 9, 11, 13, 14, 15, 16, 21, 22, 37, 40, 42, 43, 44], "python": [0, 5, 6, 9, 11, 13, 14, 15, 16, 37, 39, 40, 43, 44], "hansken": [0, 1, 2, 5, 8, 9, 10, 11, 12, 14, 16, 18, 19, 21, 22, 23, 24, 25, 29, 30, 31, 32, 35, 36, 37, 39, 41, 43], "18677": 0, "ad": [0, 4, 7, 11, 12, 13, 16, 18, 21, 29, 35, 36, 39, 42], "hql": [0, 2, 11, 12, 18, 23, 32, 35, 41, 43], "lite": [0, 2, 11, 12, 18, 35, 43], "auto": 0, "escap": [0, 7, 18, 19, 35, 36], "fix": [0, 13], "fullmatch": 0, "wildcard": [0, 7], "support": [0, 3, 5, 7, 11, 12, 13, 18, 23, 34, 35, 38, 41], "19179": 0, "project": [0, 3, 11, 16, 18, 21, 22, 37, 38, 40], "depend": [0, 4, 6, 7, 9, 12, 22, 37, 38, 40, 42], "19148": 0, "improv": [0, 1, 35], "build_plugin": [0, 30, 35, 39], "test": [0, 2, 3, 6, 7, 13, 16, 17, 18, 19, 25, 35, 36, 37, 40, 41], "framework": [0, 2, 6, 7, 13, 16, 17, 25, 35, 36, 37, 40, 41], "output": [0, 12, 16, 19, 36, 37, 38, 41], "pass": [0, 12, 23, 35, 36, 38, 42], "subprocess": 0, "directli": [0, 24, 42], "termin": [0, 37, 38, 42], "19076": 0, "expos": 0, "version": [0, 7, 13, 18, 21, 22, 23, 24, 30, 35, 36, 37, 39, 43, 44], "through": [0, 4, 5, 18, 29, 35], "extractionplugincli": [0, 24], "18830": 0, "aio": [0, 2], "debugextractionplugintool": [0, 3], "doc": [0, 37], "19065": 0, "link": [0, 9, 12], "exampl": [0, 5, 6, 11, 12, 13, 15, 16, 18, 19, 21, 23, 29, 33, 35, 36, 39, 41, 42, 44], "now": [0, 5, 6, 7, 10, 18, 35, 36, 37], "host": [0, 14, 36, 37], "github": [0, 14, 37], "19064": 0, "allow": [0, 7, 8, 11, 12, 13, 18, 19, 23, 28, 30, 35, 37, 41, 43], "import": [0, 4, 13, 18, 23, 24, 35, 36, 38, 41, 42], "local": [0, 2, 6, 18, 21, 35, 39], "modul": [0, 13, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 41], "17675": 0, "enabl": [0, 3, 8, 15, 16, 19, 35, 36, 37, 38, 41], "write": [0, 4, 11, 12, 15, 16, 18, 23, 29, 35, 37, 38, 41], "stream": [0, 5, 7, 12, 23, 27, 28, 29, 31], "data": [0, 2, 4, 10, 11, 16, 18, 19, 27, 28, 29, 30, 34, 35, 36, 38, 42], "from": [0, 3, 4, 5, 7, 10, 12, 13, 16, 18, 19, 23, 24, 28, 29, 30, 35, 36, 37, 38, 41, 42], "18982": 0, "error": [0, 13, 19, 29, 36], "messag": [0, 13, 19, 23, 36, 41], "client": [0, 12, 13, 18, 24, 35, 36, 38], "side": [0, 7, 18, 35], "except": [0, 7, 24, 29], "caught": 0, "grpc": [0, 4, 12, 13, 15, 24, 42, 43], "also": [0, 3, 6, 7, 12, 13, 18, 19, 21, 28, 30, 35, 36, 37, 39, 42], "defer": [0, 18, 30, 35, 43], "18915": 0, "18907": 0, "upgrad": [0, 18, 35], "antlr": 0, "18883": 0, "multipl": [0, 3, 7, 10, 11, 12, 13, 15, 33, 41], "datastream": [0, 13, 18, 23, 28, 29, 35, 41], "trace": [0, 2, 3, 4, 5, 7, 9, 10, 11, 16, 18, 19, 27, 28, 29, 30, 31, 32, 33, 35, 36, 38, 42, 43], "concurr": [0, 3], "18673": 0, "18517": 0, "document": [0, 7, 10, 12, 13, 16, 18, 35, 38], "creat": [0, 4, 5, 9, 10, 11, 13, 16, 18, 23, 24, 29, 35, 36, 41, 42, 44], "nest": [0, 13], "18400": 0, "build": [0, 12, 18, 21, 22, 23, 24, 29, 30, 34, 37, 39, 40, 41, 42], "tox": [0, 37, 42], "us": [0, 3, 4, 6, 8, 10, 11, 12, 13, 16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 30, 32, 35, 36, 37, 38, 39, 41, 43], "docutil": 0, "18": [0, 13], "17556": 0, "child": [0, 4, 5, 11, 12, 18, 23, 29, 35], "out": [0, 13, 19, 36, 38], "17692": 0, "experiment": 0, "add": [0, 6, 7, 12, 15, 16, 18, 19, 23, 29, 34, 36, 37, 41], "preview": [0, 44], "extend": [0, 12, 19, 23, 24, 41], "them": [0, 4, 6, 7, 12, 13, 15, 23, 41, 42], "well": [0, 6, 7, 10, 16, 36, 38, 39], "17742": 0, "rais": [0, 29], "filenotfounderror": 0, "instead": [0, 3, 5, 7, 8, 18, 19, 35, 36], "log": [0, 6, 7, 13, 38], "an": [0, 3, 5, 9, 10, 12, 13, 15, 16, 17, 18, 21, 23, 24, 25, 29, 30, 31, 35, 38, 39, 40, 41, 42, 43], "": [0, 3, 7, 9, 12, 13, 18, 23, 30, 34, 35, 38, 41, 42], "file": [0, 5, 7, 10, 12, 13, 15, 16, 18, 19, 23, 24, 29, 35, 36, 39, 41, 42], "doe": [0, 13, 15, 21, 29, 35, 41], "exist": [0, 7, 11, 13, 29, 41], "17786": 0, "48": [0, 38], "cve": 0, "2022": 0, "3509": 0, "17636": 0, "explan": [0, 7, 10], "match": [0, 4, 10, 11, 12, 13, 18, 19, 24, 35, 36, 41, 43], "type": [0, 2, 4, 6, 10, 12, 16, 18, 19, 23, 27, 28, 29, 33, 35, 36, 37, 39, 41], "17672": 0, "nice": 0, "jb": 0, "cartoon": [0, 44], "land": 0, "17502": 0, "org": [0, 10, 12, 13, 18, 19, 22, 23, 24, 35, 36, 37, 43], "info": [0, 9, 11, 23, 35, 38, 41, 42, 43], "id": [0, 3, 4, 13, 18, 19, 21, 29, 30, 35, 36, 38, 43], "label": [0, 18, 21, 23, 30, 35, 39, 43], "17460": 0, "flit": [0, 12, 19, 24, 42], "17265": 0, "paramet": [0, 11, 12, 28, 29, 31, 32, 35, 41], "py": [0, 15, 25, 30, 31, 35, 36, 37, 39, 41, 42], "docker": [0, 4, 6, 18, 21, 22, 24, 30, 35, 39, 40], "command": [0, 6, 18, 19, 21, 35, 36, 37, 39, 42], "proxi": [0, 21, 35, 37], "set": [0, 3, 4, 6, 8, 11, 13, 18, 23, 24, 29, 35, 41, 42], "17264": 0, "remov": [0, 18, 19, 23, 24, 35, 36, 41], "portutil": 0, "listen": [0, 36], "port": [0, 3, 19, 24, 36, 42, 43], "17276": 0, "move": [0, 35], "extern": [0, 9, 35, 40, 44], "get": [0, 1, 13, 15, 18, 23, 25, 29, 31, 35, 41, 42], "start": [0, 3, 5, 11, 12, 15, 18, 19, 24, 25, 29, 34, 35, 36, 38, 39, 43, 44], "17274": 0, "broken": 0, "websit": [0, 37], "17278": 0, "url": [0, 22, 24, 30, 38], "licens": [0, 4, 15, 18, 22, 30, 35, 43], "packag": [0, 13, 17, 18, 19, 22, 25, 26, 35, 36, 37, 40], "17203": 0, "publish": [0, 19, 21, 22, 36, 39, 40], "maven": [0, 18, 21, 22, 24], "central": [0, 18, 22], "17277": 0, "rearrang": 0, "code": [0, 6, 8, 11, 12, 13, 15, 17, 18, 24, 25, 35, 37, 42, 43, 44], "snippet": [0, 17, 22, 25, 37, 44], "17273": 0, "checkstyl": 0, "17214": 0, "issu": 0, "static": [0, 23, 24, 34], "analysi": 0, "17196": 0, "downgrad": 0, "compat": [0, 15, 18, 31, 35], "level": [0, 23, 30, 41], "17194": 0, "latest": [0, 18, 21, 35, 39, 44], "16781": 0, "need": [0, 3, 4, 6, 7, 9, 11, 12, 15, 30, 35, 37, 38, 39, 42], "self": [0, 35, 41, 42], "1st": 0, "arg": [0, 31, 39], "plugininfo": [0, 9, 18, 21, 23, 24, 28, 30, 35, 39, 41], "17191": 0, "quicklink": 0, "index": [0, 3, 13, 29, 32], "16756": 0, "read": [0, 4, 10, 11, 15, 16, 18, 23, 29, 35, 37, 44], "16705": 0, "declar": [0, 35], "resourc": [0, 7, 9, 18, 30, 35, 43, 44], "queri": [0, 7, 11, 12, 19, 23, 32, 35, 36, 38, 41], "context": [0, 7, 18, 23, 27, 35, 41], "runtim": [0, 12, 24, 26, 35, 38], "17151": 0, "store": [0, 5, 7, 12, 13, 23, 41], "imag": [0, 3, 4, 5, 6, 7, 11, 18, 21, 23, 24, 30, 35, 39, 41, 43], "16753": 0, "17178": 0, "tracelet": [0, 18, 29, 35, 38], "contain": [0, 6, 7, 10, 12, 13, 15, 16, 18, 19, 21, 23, 24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 38, 39, 40, 43], "work": [0, 6, 18, 35, 36, 37, 41], "17141": 0, "matcher": [0, 4, 11, 13, 18, 30, 35, 38, 43], "run_with_hanskenpi": [0, 38], "16908": 0, "health": 0, "servic": [0, 21, 36, 42, 43], "17138": 0, "avoid": 0, "run": [0, 3, 4, 6, 8, 9, 12, 15, 18, 21, 23, 24, 25, 28, 30, 35, 36, 37, 40, 41, 42, 43], "same": [0, 3, 7, 9, 10, 11, 12, 18, 24, 35, 37, 42], "hostnam": 0, "14755": 0, "wrapper": [0, 12, 42], "manual": [0, 24, 35], "16905": 0, "16901": 0, "exclud": 0, "old": [0, 18, 35], "guava": 0, "abl": [0, 4, 12, 16, 29, 37], "remoteextractionpluginflit": [0, 24], "intellij": [0, 19], "16900": 0, "testtracesearch": 0, "return": [0, 4, 7, 11, 18, 19, 23, 24, 28, 29, 31, 32, 34, 35, 37, 41, 42, 43], "search": [0, 4, 7, 11, 12, 13, 18, 23, 29, 31, 32, 35, 38, 41], "natur": 0, "sort": [0, 13], "name": [0, 2, 4, 12, 13, 18, 19, 21, 22, 23, 27, 29, 30, 31, 33, 35, 36, 39, 41, 43], "16725": 0, "16764": 0, "correct": [0, 7, 24, 38], "16704": 0, "dataclass": 0, "where": [0, 7, 10, 13, 23, 37, 41], "applic": [0, 7, 13, 19, 23, 29, 39, 41], "17064": 0, "pack": 0, "check": [0, 4, 6, 7, 12, 13, 37], "sequenc": 0, "map": [0, 12, 13, 29, 33, 36], "valu": [0, 7, 12, 13, 29, 30, 33, 38, 41], "16638": 0, "16576": 0, "vector": [0, 18, 23, 35], "16707": 0, "16575": 0, "16574": 0, "common": [0, 7, 23, 37], "16632": 0, "execut": [0, 8, 11, 13, 18, 35, 36, 38, 42], "meta": [0, 13, 30], "runner": [0, 12, 35, 41, 42], "which": [0, 4, 6, 7, 11, 12, 13, 16, 23, 24, 29, 36, 37, 38, 39, 40, 41, 42], "fail": [0, 12, 13, 24, 37], "expect": [0, 6, 12, 16, 37, 41, 43], "16634": 0, "forward": [0, 10, 41], "introduc": [0, 18, 35], "16489": 0, "let": [0, 4, 7, 12, 19], "pipelin": [0, 40], "artifact": 0, "commun": [0, 1, 4, 8, 16, 18, 24, 36, 43, 44], "16558": 0, "serv": [0, 24, 42], "develop": [0, 1, 4, 7, 10, 11, 18, 35, 36, 37, 38, 43], "friendli": 0, "wai": [0, 6, 7, 12, 16, 18, 23, 24, 31, 35, 36, 37, 38, 41, 42], "incompat": [0, 7], "warn": [0, 13, 38, 41], "In": [0, 7, 13, 15, 16, 19, 23, 36, 37, 41, 42], "One": [0, 2, 16, 30, 42], "16403": 0, "markdownlint": 0, "e": [0, 7, 10, 16, 18, 19, 21, 23, 29, 35, 39, 42], "16268": 0, "term": 0, "default": [0, 3, 8, 9, 11, 18, 19, 23, 28, 29, 35, 37, 39, 41, 42], "properti": [0, 4, 6, 9, 11, 12, 18, 19, 22, 23, 29, 33, 35, 36, 38], "16258": 0, "jenkin": 0, "16257": 0, "stop": [0, 4], "16229": 0, "correctli": [0, 23, 37], "zip": [0, 10, 37], "16095": 0, "note": [0, 5, 7, 10, 11, 12, 13, 15, 21, 23, 24, 29, 30, 35, 37, 38, 39, 41, 42], "15961": 0, "redund": 0, "debug": [0, 2, 17, 25, 37, 41, 42], "16111": 0, "testframework": [0, 12], "skip": [0, 37], "scan": [0, 4, 6, 10], "input": [0, 18, 19, 24, 35, 36, 41, 42], "16128": 0, "disallow": 0, "overwrit": [0, 7], "similar": [0, 3, 7, 13], "16191": 0, "commit": 0, "could": [0, 7, 13, 16, 23, 41], "retriev": [0, 4, 13, 29, 30, 31], "curl": 0, "didn": 0, "t": [0, 1, 7, 12, 13, 15, 24, 35, 37, 43, 44], "due": [0, 12], "bad": 0, "16116": 0, "16105": 0, "seek": 0, "beyond": 0, "eof": 0, "throw": [0, 12, 18, 24], "16160": 0, "remain": 0, "junit": [0, 24], "16118": 0, "option": [0, 3, 12, 13, 15, 16, 18, 30, 35, 36, 37, 38, 39, 41, 42, 43], "autom": [0, 21, 22, 37, 39], "16012": 0, "were": [0, 7, 13], "determin": [0, 4], "16238": 0, "server": [0, 1, 12, 19, 24, 43], "line": [0, 19, 36], "increas": [0, 9, 41], "15857": 0, "programm": 0, "16139": 0, "16115": 0, "log4j": [0, 23], "16": [0, 38], "2021": [0, 13, 38], "44228": 0, "15651": 0, "16001": 0, "better": [0, 18, 35], "readabl": 0, "16044": 0, "being": [0, 7, 12, 13, 19, 23, 27, 28, 29, 41], "unstash": 0, "wrong": [0, 12], "directori": [0, 35, 39, 42], "14586": 0, "16038": 0, "tool": [0, 3, 4, 7, 11, 13, 15, 16, 22, 28, 30, 36, 37], "15653": 0, "15801": 0, "agent": [0, 21], "16090": 0, "m2r2": 0, "requir": [0, 3, 7, 12, 15, 16, 18, 19, 22, 23, 31, 35, 36, 38, 40, 41, 42, 43], "mistun": 0, "15771": 0, "mismatch": [0, 12], "model": [0, 2, 7, 12, 41], "16048": 0, "wa": [0, 7, 10, 13, 18, 19, 35], "becaus": [0, 5, 7, 11, 19, 35, 36, 41], "locat": [0, 13, 35, 37], "_build": 0, "16037": 0, "repair": 0, "15858": 0, "javadoc": [0, 17, 23], "15656": 0, "expand": [0, 4], "faq": [0, 44], "15993": 0, "isverboseloggingen": [0, 19], "method": [0, 7, 18, 19, 23, 24, 28, 32, 35, 37, 41, 42, 43], "extractionpluginflit": [0, 19], "verbos": [0, 19, 36], "desir": [0, 23], "15766": 0, "mention": [0, 37], "guid": [0, 6, 15, 37], "gitlab": 0, "15765": 0, "anatomi": [0, 2], "15772": 0, "15770": 0, "15773": 0, "linter": 0, "15964": 0, "test_plugin": [0, 36, 42], "15913": 0, "traceuid": 0, "traceid": 0, "15668": 0, "bugfix": 0, "prevent": [0, 7], "crash": [0, 7], "result": [0, 4, 7, 19, 23, 24, 29, 31, 32, 36, 41], "differ": [0, 3, 4, 7, 10, 11, 12, 13, 18, 26, 28, 29, 33, 35, 37], "15871": 0, "rpcunixtim": 0, "rpczoneddatetim": 0, "pars": [0, 7, 10], "incorrectli": 0, "15745": 0, "convent": [0, 2, 18, 35, 43], "convieni": 0, "domain": [0, 6, 10, 18, 30, 35], "categori": [0, 10, 13, 18, 30, 35], "plugininfobuild": 0, "15790": 0, "pin": 0, "polici": 0, "discov": 0, "mypi": 0, "protobuf": 0, "15650": 0, "gener": [0, 4, 7, 12, 13, 18, 19, 21, 35, 36, 39, 44], "concept": [0, 7, 44], "15743": 0, "should": [0, 6, 12, 13, 15, 18, 29, 35, 37, 41, 42, 43], "limit": [0, 3, 7, 8, 13, 23, 38, 41], "2g": 0, "15846": 0, "15711": 0, "pluginresourc": [0, 18, 23, 30, 35, 41], "cpu": [0, 7, 9, 18, 23, 30, 35, 41], "memori": [0, 9, 18, 23, 30, 35, 41], "float": [0, 13, 30], "15683": 0, "enrich": 0, "ensur": [0, 8, 23], "transform": [0, 2, 11, 12, 18, 29, 35, 38], "handl": [0, 21], "befor": [0, 3, 6, 7, 13, 16, 21, 29, 35, 38], "15589": 0, "abil": [0, 12], "specifi": [0, 5, 9, 13, 18, 21, 35], "15588": 0, "renam": [0, 18, 35], "deprec": [0, 18, 35], "15641": 0, "pluginid": [0, 18, 21, 30, 35, 39], "slash": [0, 7, 10, 19, 36], "backward": [0, 18, 35], "15607": 0, "explicitli": [0, 18, 29, 35], "number": [0, 3, 4, 7, 9, 12, 13, 23, 29, 30, 31, 32, 41], "provid": [0, 6, 8, 9, 11, 12, 13, 23, 24, 30, 32, 34, 38, 40, 41, 42], "iter": [0, 4, 7, 11, 30, 31, 41, 43], "i": [0, 1, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 16, 18, 19, 21, 23, 24, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44], "invalid": [0, 7, 19, 36], "15632": 0, "field": [0, 4, 10, 12, 18, 35], "15542": 0, "consist": [0, 10, 11, 12, 23, 30, 34, 41], "15466": 0, "15572": 0, "includ": [0, 4, 7, 11, 12, 23, 24, 42], "path": [0, 13, 18, 19, 24, 35, 36, 37, 39, 42], "tracetojson": 0, "15034": 0, "15541": 0, "15562": 0, "deseri": 0, "intrins": 0, "15365": 0, "15527": 0, "15393": 0, "datatransform": 0, "rangedtransform": [0, 34, 35, 41], "rangeddatatransform": [0, 18, 23], "15391": 0, "rang": [0, 5, 7, 12, 18, 23, 34, 35, 41], "onli": [0, 3, 5, 7, 8, 11, 12, 13, 18, 19, 23, 28, 29, 31, 34, 35, 36, 38, 43], "15390": 0, "proto": 0, "definit": [0, 27, 30, 32, 33, 34, 43], "15392": 0, "15520": 0, "dedic": 0, "per": [0, 3, 5, 9, 16, 18, 35], "15515": 0, "author": [0, 4, 18, 19, 30, 35, 36, 38, 43], "maturitylevel": [0, 18, 30, 35], "15514": 0, "refactor": 0, "extractioncontext": [0, 18, 23, 35], "datacontext": [0, 18, 23, 27, 28, 35], "15512": 0, "some": [0, 3, 5, 7, 10, 12, 13, 18, 37, 38, 41, 42], "intern": [0, 7], "ep": [0, 13], "15511": 0, "dev": [0, 7, 18, 35], "15505": 0, "cleanup": 0, "unus": 0, "posit": [0, 23, 35], "unrequir": 0, "datatyp": [0, 7, 11, 18, 19, 23, 35, 36, 41], "15491": 0, "remot": [0, 19, 29, 36], "current": [0, 3, 5, 7, 11, 12, 13, 19, 23, 34, 36, 38, 41, 43], "15502": 0, "testrandomaccessdata": 0, "accept": [0, 7, 13, 23, 36, 41, 42, 43], "15495": 0, "make": [0, 6, 7, 12, 13, 15, 16, 24, 29, 35, 36, 37, 38, 39, 43], "getdata": 0, "lazi": 0, "replac": [0, 7, 18, 35, 38, 41, 42], "getalldata": 0, "getdatatyp": 0, "15498": 0, "isort": 0, "configur": [0, 18, 21, 23, 35, 37, 41], "15029": 0, "request": [0, 1, 4, 11, 15, 29, 30, 31, 36, 38], "count": [0, 7, 13, 23, 32, 41], "15274": 0, "15035": 0, "15288": 0, "subclass": 0, "15042": 0, "research": 0, "implement": [0, 4, 6, 7, 13, 15, 16, 18, 23, 24, 26, 34, 36, 40, 41, 42, 43], "15139": 0, "process": [0, 5, 7, 9, 10, 11, 12, 13, 18, 23, 24, 27, 28, 29, 31, 35, 38, 41, 42, 43], "15015": 0, "15236": 0, "initi": [0, 43], "chunk": [0, 41], "along": 0, "rpc": 0, "15338": 0, "15370": 0, "arrayoutofboundsexcept": 0, "byte": [0, 7, 13, 23, 29, 34, 41], "left": [0, 7], "prefil": 0, "cach": 0, "15353": 0, "valid": [0, 4, 7, 12, 37, 42], "without": [0, 11, 12, 13, 24, 28, 29, 35, 37, 39], "partial": 0, "startswith": 0, "containsinord": 0, "15231": 0, "caffein": 0, "block": [0, 36], "randomaccessdata": 0, "15276": 0, "heterogen": 0, "15282": 0, "bufferedread": [0, 29, 41], "15294": 0, "avail": [0, 3, 4, 5, 6, 7, 8, 13, 18, 21, 23, 29, 31, 36, 41, 42, 43], "15233": 0, "send": 0, "15232": 0, "mechan": 0, "size": [0, 27, 29, 34, 35], "mb": [0, 7, 43], "speed": 0, "up": [0, 4, 12, 15, 29, 35, 36, 38, 42], "larg": [0, 41], "15193": 0, "15237": 0, "after": [0, 4, 7, 11, 15, 19, 29, 36], "15187": 0, "15189": 0, "15192": 0, "superpom": [0, 18, 21], "15186": 0, "mimetyp": [0, 7, 19, 35, 36], "14820": 0, "receiv": [0, 13, 41], "ha": [0, 7, 10, 12, 13, 18, 19, 21, 23, 35, 36, 37, 38, 39, 41, 42, 43], "been": [0, 7, 12, 18, 21, 35, 37, 38, 39, 42], "yet": [0, 12, 15, 29, 38], "15059": 0, "skeleton": 0, "15048": 0, "worker": [0, 3], "argument": [0, 21, 35, 36, 38, 39, 41, 42], "extractionpluginserv": [0, 24], "14787": 0, "rout": 0, "header": 0, "loadbalanc": 0, "14923": 0, "window": [0, 16, 42], "14867": 0, "long": [0, 3, 13], "14879": 0, "pypi": [0, 40], "14738": 0, "shade": [0, 13], "nfi": [0, 10, 18, 21, 24, 35, 39], "jar": [0, 12, 42], "14841": 0, "rpcstringmap": 0, "wasn": 0, "unpack": 0, "properli": 0, "dure": [0, 3, 4, 7, 8, 12, 13, 15, 29, 38, 43], "14703": 0, "exit": [0, 12], "gracefulli": 0, "sigterm": 0, "14844": 0, "test_framework": [0, 42], "14793": 0, "extractionpluginbuild": 0, "add_data": [0, 29], "14777": 0, "extra": [0, 7, 21], "ignor": 0, "unsupport": [0, 3, 7], "rpcstart": 0, "serial": [0, 12], "14739": 0, "distribut": [0, 15, 18, 22, 43], "under": [0, 7, 12, 13, 15, 18, 22, 35, 37, 43], "apach": [0, 15, 18, 22, 35], "14763": 0, "load": [0, 6, 18, 35, 37], "14737": 0, "14720": 0, "open": [0, 13, 29, 37, 38, 41, 43], "14582": 0, "14660": 0, "_test": 0, "repo": [0, 22], "14618": 0, "unexpect": [0, 7], "14704": 0, "super": [0, 22, 24, 34], "pom": [0, 18, 22, 24], "14619": 0, "propag": 0, "ioexcept": [0, 18], "callback": 0, "14632": 0, "raw": [0, 7, 12, 13, 18, 19, 23, 29, 35, 36, 41], "14591": 0, "split": [0, 12], "three": [0, 10, 11, 12, 31, 33, 38], "14580": 0, "14635": 0, "format": [0, 10, 16, 18, 23], "can": [0, 1, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 16, 18, 19, 21, 22, 23, 24, 26, 28, 29, 30, 31, 32, 33, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44], "14131": 0, "14130": 0, "standalonetestrunn": 0, "13784": 0, "14581": 0, "capabl": [0, 4], "14547": 0, "deploi": [0, 37, 40], "sourc": [0, 5, 15, 18, 27, 28, 29, 30, 31, 32, 33, 34, 35, 43], "14531": 0, "one": [0, 4, 7, 9, 11, 12, 19, 28, 30, 31, 33, 36, 37, 38, 41], "step": [0, 3, 6, 15, 18, 19, 35, 36], "either": [0, 7, 29], "snapshot": 0, "separ": [0, 7, 9, 12], "merg": 0, "first": [0, 6, 12, 13, 15, 29, 31, 35, 36, 37], "repositori": [0, 14], "longer": [0, 7, 10, 18, 23, 35], "sinc": [0, 7], "actual": [0, 5, 7, 11, 12, 13, 16, 18, 35, 37], "between": [0, 4, 7, 12, 24, 43], "apart": 0, "scheme": 0, "download": 0, "comment": 0, "jenkinsfil": 0, "14161": 0, "around": [0, 7], "tgz": 0, "whl": 0, "14234": 0, "restructur": 0, "13799": 0, "14286": 0, "adapt": [0, 9, 36], "inputstream": 0, "14314": 0, "don": [0, 1, 7, 12, 13, 15, 24, 35, 37, 43, 44], "14318": 0, "flush": 0, "children": [0, 12, 13], "root": [0, 8, 13, 23, 37, 38], "case": [0, 5, 7, 11, 12, 13, 15, 16, 18, 23, 35, 37, 39], "13414": 0, "netti": 0, "14283": 0, "retri": 0, "14135": 0, "integr": [0, 12, 19, 24, 42, 44], "14134": 0, "14128": 0, "instanc": [0, 3, 4, 12, 22, 23, 42], "14122": 0, "miss": [0, 10], "comma": 0, "broke": 0, "14092": 0, "total": [0, 27, 31], "object": [0, 4, 13, 23, 27, 29, 30, 32, 33, 34, 35, 41], "13668": 0, "latlong": [0, 13], "14104": 0, "64": 0, "overhead": [0, 7], "14079": 0, "14010": 0, "datetim": [0, 13, 41], "14083": 0, "14035": 0, "14030": 0, "standalon": [0, 36], "non": [0, 13], "14073": 0, "14074": 0, "14090": 0, "call": [0, 4, 6, 7, 12, 13, 23, 28, 31, 36, 41, 42, 43], "13774": 0, "13776": 0, "base": [0, 7, 9, 10, 12, 13, 22, 23, 27, 28, 29, 30, 31, 32, 33, 34, 41], "14060": 0, "interfac": [0, 6, 13, 24], "13773": 0, "14044": 0, "sure": [0, 6, 7, 12, 13, 15, 24, 35, 37, 39, 43], "lint": 0, "enforc": 0, "singl": [0, 7, 11, 12, 13, 31, 33, 41], "quot": [0, 7, 39], "13772": 0, "13775": 0, "14031": 0, "v": [0, 38, 41], "14037": 0, "pytest": 0, "alwai": [0, 7, 11, 12, 13, 23, 41], "14011": 0, "14009": 0, "14008": 0, "parser": [0, 7], "conflict": 0, "13777": 0, "util": [0, 9, 13, 23, 24, 39], "13771": 0, "13966": 0, "give": [0, 10, 16, 37, 42], "socketproxi": 0, "disconnect": 0, "time": [0, 3, 7, 11, 12, 13, 15, 21, 35, 37], "flaki": 0, "unit": [0, 12, 24, 30, 42], "13810": 0, "13676": 0, "zoneddatetim": 0, "over": [0, 4, 5, 7, 8, 10, 42], "13922": 0, "webpag": [0, 37, 43], "consum": [0, 7, 18], "13655": 0, "13809": 0, "13801": 0, "made": 0, "immut": [0, 29], "13761": 0, "seper": 0, "13756": 0, "13800": 0, "temporarili": 0, "13798": 0, "failur": 0, "13706": 0, "basic": [0, 7, 38], "13769": 0, "13705": 0, "13713": 0, "visibl": [0, 3, 4], "13709": 0, "13733": 0, "script": [0, 18, 30, 35, 36, 42], "13660": 0, "copi": 0, "13656": 0, "protocol": [0, 4, 36], "13663": 0, "13714": 0, "extractionplugin": [0, 18, 19, 23, 24, 28, 35, 42], "13658": 0, "connect": [0, 19], "13650": 0, "inform": [0, 4, 6, 11, 13, 15, 16, 18, 23, 28, 30, 35, 36, 37, 41], "13648": 0, "13651": 0, "13643": 0, "13581": 0, "13580": 0, "13579": 0, "13577": 0, "Be": [0, 3, 23, 35, 37, 41], "13578": 0, "13560": 0, "13554": 0, "pleas": [1, 7, 11, 13, 15, 18, 35, 37, 38, 43, 44], "touch": 1, "u": [1, 12, 15, 21, 44], "you": [1, 3, 6, 7, 11, 12, 13, 18, 21, 23, 24, 35, 37, 38, 39, 40, 41, 42, 43, 44], "have": [1, 5, 6, 7, 9, 11, 12, 13, 15, 18, 21, 23, 35, 37, 38, 40, 41, 42, 43], "question": [1, 7, 44], "about": [1, 4, 6, 7, 11, 13, 23, 28, 30, 41, 43], "found": [1, 11, 12, 13, 14, 18, 19, 23, 26, 32, 35, 36, 38, 42, 43], "bug": [1, 7, 19, 36], "featur": [1, 3, 6, 7, 11], "other": [1, 3, 4, 7, 11, 12, 13, 16, 18, 28, 29, 35, 38, 44], "opportun": 1, "want": [1, 7, 37, 38, 42], "contribut": 1, "chat": [1, 18, 21, 35, 39, 41, 42, 44], "discord": [1, 37, 44], "find": [1, 6, 7, 13, 15, 16, 19, 23, 36, 38, 41], "member": [1, 16, 44], "team": [1, 10, 15, 43], "channel": [1, 37], "thi": [1, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 19, 21, 23, 24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 41, 42, 43, 44], "privat": [1, 23, 24], "If": [1, 3, 4, 6, 7, 9, 10, 11, 12, 13, 15, 18, 19, 21, 23, 35, 36, 37, 38, 39, 41, 42, 43, 44], "access": [1, 13, 16, 18, 23, 28, 35, 37, 41, 44], "busi": [1, 15], "owner": [1, 15], "he": [1, 7], "she": 1, "invit": 1, "know": [1, 4, 7, 15], "who": [1, 15, 37], "feel": [1, 15, 37], "free": [1, 4, 10, 15, 37], "fill": 1, "form": [1, 10, 13, 15], "further": [1, 6, 7, 13, 35], "isol": [2, 15], "kubernet": [2, 4, 8, 15], "autosc": [2, 4], "resourcemanag": 2, "46": 3, "4": [3, 22, 24, 36, 37], "0": [3, 7, 12, 13, 15, 19, 21, 22, 23, 24, 29, 30, 36, 37, 41], "deferredextractionplugin": [3, 23, 28, 41], "NOT": [3, 7, 11], "It": [3, 6, 7, 12, 13, 15, 18, 21, 23, 35, 37, 38, 41], "possibl": [3, 7, 11, 12, 13, 15, 16, 18, 19, 21, 23, 24, 35, 36, 41], "few": [3, 13, 33, 37, 38], "8999": [3, 24, 42, 43], "breakpoint": 3, "prepar": 3, "advanc": [3, 12, 25, 35, 37], "happi": 3, "1": [3, 7, 11, 12, 13, 19, 22, 23, 30, 36, 41], "myplugin": [3, 24, 38], "disabl": 3, "small": 3, "otherwis": 3, "might": [3, 7, 23, 37, 38, 41], "take": [3, 5, 6, 7, 15, 31, 37, 41], "reach": [3, 9], "care": [3, 7, 23, 41], "debugg": [3, 19, 36], "append": [3, 23], "onc": [3, 7, 13, 21, 29, 31, 39], "so": [3, 4, 7, 13, 16, 19, 36], "re": [3, 7], "session": 3, "we": [3, 7, 15, 23, 37, 41, 42], "advis": 3, "everi": [3, 4, 7, 11, 12, 13, 28], "hit": [3, 10, 31, 37, 38], "thread": [3, 9, 23], "restart": [3, 9], "produc": [3, 7, 11, 13, 19, 36], "undefin": 3, "behaviour": 3, "its": [4, 6, 7, 10, 12, 13, 16, 28], "simplifi": [4, 7, 18, 35], "each": [4, 5, 7, 8, 9, 10, 12, 13, 18, 24, 35, 37], "must": [4, 6, 13, 19, 23, 36, 38, 41, 42], "two": [4, 7, 9, 11, 12, 18, 21, 23, 35, 39, 41, 43], "perform": [4, 7, 10, 11, 31], "task": 4, "dive": 4, "bit": [4, 19, 36, 37], "deeper": 4, "next": [4, 11, 12, 15, 19, 23, 36, 38], "section": [4, 7, 12, 13, 18, 24, 35, 37, 38, 42], "show": [4, 5, 10, 12, 13, 18, 19, 23, 35, 37, 38, 41], "most": [4, 7, 11, 23, 31, 32, 41], "identifi": [4, 7, 13, 18, 30, 35], "uniqu": [4, 7, 10, 12, 13, 30], "descript": [4, 10, 13, 18, 30, 35, 43], "A": [4, 6, 7, 10, 11, 12, 13, 18, 29, 30, 31, 33, 34, 35, 36, 38, 42], "shown": [4, 10, 12, 19, 23, 36], "sent": 4, "attribut": 4, "veri": [4, 7, 11, 12], "although": [4, 5, 18, 35], "whatev": 4, "seem": 4, "typic": [4, 13, 41], "within": [4, 8, 9, 11, 41, 43], "repres": [4, 13, 18, 23, 29, 30, 31, 35, 41], "perspect": 4, "user": [4, 6, 18, 21, 30, 35, 37, 38], "manag": [4, 9, 18, 37, 41], "select": [4, 6, 30, 37, 38], "To": [4, 6, 7, 8, 12, 13, 16, 18, 19, 21, 23, 24, 35, 36, 37, 38, 39, 41, 42], "accomplish": 4, "registri": [4, 6, 16, 18, 21, 35, 39], "done": [4, 6, 7, 9, 12, 24, 37, 41], "shutdown": 4, "again": [4, 7], "ani": [4, 7, 12, 13, 29, 33, 41, 44], "least": [4, 11], "loop": [4, 12], "mean": [4, 7, 12, 13, 24, 37, 43], "via": [4, 6, 19, 36], "like": [4, 7, 18, 19, 23, 29, 33, 36, 37, 39, 41], "written": [4, 12, 15, 37], "At": [4, 24, 36, 38], "end": [4, 24, 30, 35, 37], "associ": 4, "obtain": [5, 11, 18, 28, 35], "prefer": [5, 10], "blob": 5, "thei": [5, 7, 11, 12, 13, 16, 31, 39], "less": [5, 31], "space": [5, 13, 18, 35, 39], "figur": [5, 13], "visual": [5, 36], "appli": [5, 7, 10, 13, 16, 21, 43], "moment": [5, 38], "while": [5, 7, 9, 11, 12, 19, 36], "archiv": [5, 10, 13, 18, 35], "entri": [5, 13, 16, 18, 35], "mark": [5, 13, 18, 35, 41], "length": [5, 12, 18, 23, 27, 34, 35, 41], "origin": [5, 13, 16, 18, 35], "By": [5, 7, 18, 35, 42], "just": [5, 18, 35, 38], "lot": [5, 7, 18, 35], "save": [5, 11, 18, 29, 35, 38], "variou": [5, 12, 18, 23, 35], "defin": [5, 6, 7, 12, 13, 18, 35, 41, 43], "offset": [5, 12, 18, 23, 29, 34, 35, 41], "bytearrai": [5, 13, 18, 35], "own": [6, 24, 37], "built": [6, 12, 19, 29, 35, 36, 39], "choos": [6, 11, 37], "everyth": [6, 15, 37], "oci": [6, 21, 39], "known": [6, 10, 21, 38, 39], "try": [6, 7, 12, 23], "certain": [6, 11], "prefix": [6, 35], "uri": 6, "push": 6, "outlin": 6, "do": [6, 12, 13, 15, 24, 37, 41], "login": 6, "pluginnam": [6, 12, 18, 35], "chapter": 6, "startup": [6, 7], "endpoint": [6, 13, 38], "gatekeep": [6, 13, 38], "true": [6, 7, 12, 19, 24], "invok": [6, 12, 21, 35], "internet": 6, "browser": 6, "went": 6, "checkbox": 6, "dialog": [6, 38], "deriv": [7, 10, 13, 28], "full": [7, 10, 12, 13, 18, 35], "human": [7, 13], "stand": 7, "element": [7, 13, 16], "lightweight": 7, "usag": [7, 9, 35, 37, 41], "sai": 7, "hansken_image1": 7, "10": [7, 18, 23, 35, 37, 41], "pdf": [7, 13], "5": [7, 23, 30, 36, 37, 41], "jpeg": [7, 12], "And": 7, "our": [7, 37], "2": [7, 13, 15, 22, 23, 30, 36, 37, 41], "jpegtool": 7, "core": [7, 18, 23, 30, 35, 41, 43], "look": [7, 11, 33, 37, 38], "pseudocod": 7, "inner": 7, "new_trac": 7, "hansken_tool": 7, "process_the_trac": 7, "here": [7, 11, 13, 15, 21, 23, 24, 36, 37, 41, 42, 44], "answer": 7, "part": [7, 12, 13, 37, 41, 43], "mani": [7, 12, 13, 24, 30, 37, 39], "than": [7, 12, 13, 15, 29, 37], "15": 7, "usual": [7, 13], "deal": [7, 41], "million": 7, "second": [7, 13, 31], "would": [7, 12], "11": [7, 22, 37, 40, 42], "dai": 7, "reduc": 7, "unnecessari": 7, "even": [7, 41], "ones": 7, "cannot": [7, 39], "condit": 7, "sometim": [7, 11], "simpl": [7, 23], "filenam": 7, "often": [7, 12], "elabor": 7, "sens": [7, 10], "factor": 7, "intim": 7, "knowledg": [7, 12, 13, 16], "said": 7, "subset": [7, 23, 41], "plu": 7, "interest": [7, 33, 43], "though": 7, "compil": [7, 43], "achiev": 7, "design": [7, 8, 16], "elasticsearch": 7, "databas": [7, 10], "As": 7, "tightli": 7, "coupl": [7, 29], "difficult": 7, "complex": 7, "minor": 7, "absolut": 7, "necessari": 7, "much": [7, 12, 30, 38], "point": [7, 13, 34], "view": [7, 13, 19, 36, 39], "finish": [7, 12, 37], "activ": 7, "remark": 7, "empti": [7, 16], "string": [7, 12, 13, 18, 35, 41], "translat": 7, "foo": 7, "AND": [7, 23, 41], "bar": [7, 38], "sensit": [7, 23, 41], "oper": [7, 9], "behav": 7, "logic": [7, 13, 16], "Not": 7, "negat": 7, "express": [7, 38], "min": 7, "max": [7, 30], "Or": 7, "OR": 7, "below": [7, 10, 12, 13, 18, 19, 24, 35, 36, 37, 42], "against": [7, 10, 12, 24, 42], "email": [7, 30, 43], "There": [7, 12, 24, 36, 38], "guidelin": 7, "equal": 7, "right": [7, 13, 44], "statement": [7, 18, 19, 23, 35, 36], "opposit": 7, "charact": [7, 10, 18, 35], "g": [7, 10, 16, 18, 21, 23, 29, 35, 39], "r": 7, "w": 7, "row": [7, 12], "char": 7, "ra": 7, "raaaaaw": 7, "aw": 7, "exact": 7, "surround": 7, "tell": [7, 36], "especi": [7, 35], "help": [7, 16, 37, 38], "hello": [7, 41], "csv": 7, "chatmessag": [7, 41], "both": [7, 12, 13, 14, 23, 29, 35], "group": [7, 8, 13, 15, 35], "put": [7, 12, 19, 36, 39], "bracket": 7, "bla": 7, "3": [7, 19, 23, 24, 37, 40], "n": [7, 13, 19, 23, 35, 37, 44], "kei": [7, 12, 13, 18, 29, 35], "pair": [7, 13], "prepend": 7, "etc": [7, 23, 29], "rule": [7, 36], "unix": [7, 21], "baz": 7, "llo": 7, "unaccept": 7, "regex": 7, "c": [7, 12, 37], "colon": 7, "still": [7, 12, 15, 18, 35], "backslash": 7, "univers": 7, "content": [7, 10, 18, 35, 38], "discuss": [7, 12, 15], "elsewher": 7, "idea": 7, "represent": [7, 31], "text": [7, 10, 12, 16, 29, 41], "explain": [7, 12, 38], "subsequ": 7, "reason": 7, "lettercountplugin": 7, "letter": 7, "choic": [7, 37], "too": [7, 12], "tediou": 7, "flexibl": 7, "compact": 7, "plain": 7, "x": 7, "encod": [7, 22, 23], "But": 7, "txt": [7, 22, 35], "summar": [7, 18, 35], "word": 7, "go": [7, 37, 38], "checklist": 7, "effect": [7, 18, 35], "easiest": 7, "someth": [7, 23], "cours": [7, 9], "un": 7, "intention": 7, "misnam": 7, "wikipedia": 7, "portion": 7, "beforehand": 7, "broad": 7, "scope": [7, 35, 43], "becom": 7, "huge": 7, "accord": 7, "good": [7, 13, 15, 37], "cutoff": 7, "1000000": 7, "uncommon": 7, "overlap": 7, "documentplugin": 7, "alreadi": [7, 19, 23, 29, 38, 41], "solut": 7, "encrypt": 7, "tmp": 7, "viru": 7, "promis": 7, "recommend": [7, 10, 18, 19, 22, 35, 36, 37], "filetyp": [7, 13], "mimeclass": [7, 12, 35], "paragraph": [7, 13], "loos": 7, "tight": 7, "yield": 7, "reliabl": 7, "noth": 7, "mai": [7, 12, 23, 33, 37, 38, 41], "decrypt": [7, 13], "intend": [7, 43], "contrarili": 7, "someon": 7, "think": [7, 15], "ll": 7, "b": [7, 12, 13, 37, 41, 44], "forget": 7, "did": 7, "potenti": 7, "those": [7, 12, 37], "edg": 7, "best": 7, "incorrect": 7, "whenev": 7, "rememb": 7, "twice": 7, "lead": 7, "doesn": [7, 12, 13], "prohibit": 7, "practic": 7, "tip": 7, "slow": 7, "extrem": 7, "fast": 7, "abov": [7, 12, 23, 37, 44], "calcul": 7, "unlik": 7, "bare": [7, 30], "minimum": 7, "far": 7, "50": [7, 11, 18, 23, 35, 41], "criteria": 7, "realli": 7, "control": 7, "arbitrari": [8, 10], "insid": [8, 12, 36, 37], "cluster": [8, 9, 15, 19, 36], "subject": 8, "principl": 8, "secur": [8, 15], "privaci": 8, "transpar": 8, "compliant": 8, "measur": [8, 30], "place": [8, 37], "restrict": 8, "impli": 8, "1000": [8, 18, 23, 35, 41], "2000": 8, "fsgroup": 8, "3000": 8, "linux": 8, "manner": 8, "platform": [8, 16, 43], "runtimedefault": 8, "comput": [8, 10, 23, 41], "mode": [8, 29, 41], "seccomp": 8, "sane": 8, "anoth": [9, 11, 13, 23, 41, 42], "pod": [9, 23], "12": [9, 38], "horizont": 9, "autoscal": 9, "hpa": 9, "replica": 9, "metric": [9, 23], "observ": 9, "system": [9, 13, 18, 21, 35, 37, 38], "scale": [9, 15, 23], "down": 9, "automat": [9, 12, 18, 35, 42], "maximum": [9, 11, 18, 23, 32, 35, 41], "node": 9, "itself": [9, 12, 13], "monitor": 9, "accordingli": [9, 35], "combin": [10, 16], "organis": [10, 15, 30, 43], "shortest": 10, "nl": [10, 15, 18, 21, 24, 35, 39], "politi": 10, "fiod": 10, "action": 10, "tabl": 10, "clear": 10, "structur": [10, 13, 35], "carv": 10, "fragment": 10, "reassembl": 10, "absenc": 10, "filesystem": 10, "metadata": [10, 13, 28, 29], "classifi": 10, "categor": 10, "detect": [10, 13, 37], "monei": 10, "pictur": [10, 13], "digest": 10, "hash": 10, "ocr": 10, "optic": 10, "recognit": [10, 19, 36], "report": 10, "whether": [10, 37], "classic": 10, "what": [10, 38], "last": [10, 19, 36], "column": 10, "previou": [10, 35, 37], "sha256": 10, "tesseract": 10, "enough": [11, 42], "addit": [11, 12, 19, 36, 37, 39, 41], "modifi": 11, "modif": 11, "regular": 11, "why": 11, "deferrediter": 11, "20": [11, 13, 18, 19, 23, 30, 35, 36, 37, 41], "given": [11, 12, 13, 28, 37, 39, 41], "syntax": [11, 18, 35, 41], "compon": 12, "independ": 12, "setup": [12, 24, 35, 36, 38, 42], "thereaft": 12, "example1": 12, "example2": 12, "folder": [12, 23, 24, 37, 38, 43], "extens": [12, 13, 23, 35, 41], "correspond": 12, "none": [12, 29, 30, 31], "succe": 12, "_": 12, "__": 12, "trace__": 12, "basenam": 12, "maintain": [12, 16, 38], "dataset": 12, "regener": [12, 19, 24, 36], "collect": [12, 13, 33], "compar": [12, 38, 42], "extrat": 12, "searchtrac": [12, 29, 31], "deferredexamplesearch": 12, "deferredexampl": 12, "deferredpluginnam": 12, "imposs": 12, "infinit": 12, "interpret": [12, 39], "howev": [12, 13, 15, 38, 42], "therefor": [12, 13], "slight": 12, "pure": 12, "purpos": 12, "dictionari": 12, "six": 12, "five": 12, "boolean": [12, 13, 19, 24], "integ": [12, 13], "doubl": [12, 13], "stringlist": 12, "d": [12, 18, 23], "compli": [12, 13], "caus": [12, 42], "depth": [12, 29], "normal": 12, "reflect": 12, "consequ": 12, "present": [12, 13], "occasion": 12, "exactli": [12, 38], "ref": [12, 23], "reserv": [12, 30, 44], "descriptor": 12, "entir": 12, "79": 12, "scenario": 12, "occur": 12, "deliber": 12, "impract": 12, "prone": 12, "circumst": 12, "irrelev": 12, "relev": 12, "class": [12, 13, 18, 19, 23, 24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 38, 41, 42], "extractionpluginexcept": 12, "lorem": 12, "ipsum": 12, "dolor": 12, "sit": 12, "amet": 12, "consectetur": 12, "adipisc": 12, "elit": 12, "mauri": 12, "faucibu": 12, "variu": 12, "sodal": 12, "incorpor": 12, "piec": [13, 36], "evid": 13, "main": [13, 23], "createdon": 13, "libr": 13, "offic": 13, "09": 13, "00": 13, "sever": [13, 36], "relat": 13, "until": [13, 36], "pattern": [13, 23, 35, 37], "datastreamtyp": 13, "propertynam": 13, "substitut": 13, "textual": 13, "adob": 13, "candid": 13, "heurist": 13, "primarili": 13, "secondarili": 13, "firefli": 13, "occas": 13, "decod": 13, "standard": [13, 37, 41], "utf": [13, 22, 23, 41], "8": [13, 22, 23, 35, 37, 40, 41], "With": [13, 16, 19, 36, 38], "parenttraceid": 13, "childnumb": 13, "parent": [13, 22, 24, 29], "9": 13, "direct": [13, 23, 35, 41], "binari": 13, "bool": 13, "int": [13, 23, 24, 27, 29, 30, 31, 32, 34], "real": 13, "str": [13, 27, 29, 30, 32, 33], "date": 13, "tupl": [13, 36], "dict": [13, 29], "geographicloc": 13, "dimension": 13, "arrai": [13, 23], "gui": 13, "nearbi": 13, "neural": 13, "network": [13, 37], "embed": [13, 18, 19, 23, 35, 43], "face": 13, "rest": [13, 38], "euclidean": 13, "manhattan": 13, "distanc": 13, "cosin": 13, "bundl": 13, "belong": 13, "cardin": 13, "fvt": [13, 33], "mvt": 13, "futur": [13, 23, 41], "releas": [13, 18, 23, 35, 41], "predict": [13, 18, 23, 35, 41], "ident": [13, 33, 35, 38], "compos": 13, "inrins": 13, "predefin": 13, "tracemodel": 13, "newli": 13, "erron": 13, "expert": [13, 38], "ui": [13, 38], "click": [13, 37], "Then": [13, 24, 35, 37, 39], "screen": 13, "displai": [13, 19, 36], "lang": [13, 19, 36], "illegalargumentexcept": 13, "this_property_does_not_exist": 13, "681": 13, "cumul": 13, "uuid": 13, "io": 13, "statusruntimeexcept": 13, "cancel": 13, "streamobserv": 13, "onerror": 13, "abort": 13, "7": [13, 36], "illegalstateexcept": 13, "unavail": 13, "http": [13, 18, 21, 22, 24, 35, 37], "no_error": 13, "rst": 13, "git": [15, 24], "sign": 15, "eminjenv": [15, 24], "account": 15, "contact": [15, 23, 37, 41, 43, 44], "faster": [15, 38], "scalabl": 15, "flexibli": 15, "jvm": 15, "scala": 15, "kotlin": 15, "probabl": 15, "effort": 15, "proper": 15, "hood": 15, "theori": 15, "ye": 15, "risk": 15, "respons": 15, "third": [15, 41, 42], "parti": 15, "consid": [15, 44], "refer": [15, 16, 18, 23, 35], "beta": 15, "assist": 15, "offici": 15, "insight": 16, "digit": 16, "seiz": 16, "demand": 16, "materi": 16, "aspect": 16, "engin": 16, "forens": [16, 38], "investig": 16, "understand": 16, "thu": 16, "crypto": 16, "currenc": 16, "wallet": 16, "disk": 16, "algorithm": 16, "speech": 16, "audio": 16, "primari": 16, "goal": 16, "easi": [16, 23, 38], "share": 16, "product": 16, "facet": 16, "later": [16, 18, 35], "verifi": [16, 23, 39, 41], "benefit": 16, "encourag": 16, "changelog": [17, 25, 44], "prerequisit": [17, 21, 25, 37], "aim": [18, 35], "appdata": [18, 35], "registryentri": [18, 35], "softwar": [18, 22, 35, 44], "dropbox": [18, 35], "k": [18, 30, 35], "p": [18, 19, 35, 36], "null": [18, 24, 35], "highli": 18, "migrat": [18, 35], "effici": [18, 23, 35, 41], "xml": [18, 22, 23], "pluginvers": 18, "builder": [18, 23, 29, 34, 35, 41], "earlier": [18, 35], "come": [18, 35, 39], "prior": [18, 35], "switch": [18, 35], "style": [18, 35], "instruct": [18, 19, 21, 35, 37, 39], "mvn": [18, 19, 21], "whatsapp": [18, 21, 35, 39], "tag": [18, 21, 35, 39], "nb": 18, "environ": [18, 19, 21, 23, 37, 38, 41], "podman": [18, 21, 22], "altern": [18, 21, 22, 23], "addtracelet": [18, 23], "classif": [18, 23], "confid": [18, 23, 35, 41], "8f": [18, 23], "modelnam": [18, 23, 41], "yolo": [18, 23, 41], "modelvers": [18, 22, 23, 41], "conveni": 18, "boilerpl": 18, "builderfor": [18, 23], "testplugin": [18, 19, 35], "vcpu": [18, 23, 30, 35, 41], "hyperthread": [18, 23, 30, 35, 41], "gb": [18, 23, 35, 41], "maximumcpu": [18, 23], "5f": [18, 23], "maximummemori": [18, 23], "proof_of_concept": [18, 30, 35], "hqlmatcher": 18, "webpageurl": 18, "www": [18, 22], "html": [18, 23, 29, 35, 41], "setdata": [18, 23], "addrang": [18, 23], "newchild": [18, 23], "linenumb": [18, 23], "30": [18, 23, 35, 41], "upcom": [18, 35], "break": [18, 35], "deferredextractioplugin": [18, 35], "stage": [18, 28, 35, 38], "searcher": [18, 23, 28, 32, 35, 41], "overrid": [18, 19, 23, 24], "public": [18, 19, 22, 23, 24, 35, 40], "void": [18, 23, 24], "final": [18, 23, 24], "art": [19, 36], "hopefulli": [19, 36], "quickli": [19, 36], "advantag": [19, 36, 42], "easili": [19, 36], "print": [19, 31, 36], "consol": [19, 23, 36, 37], "trickier": [19, 36], "bake": 19, "distinct": [19, 36], "variabl": [19, 23, 35, 37, 38, 39, 41], "java_tool_opt": 19, "agentlib": 19, "jdwp": 19, "transport": 19, "dt_socket": 19, "y": 19, "suspend": 19, "address": 19, "5005": 19, "debugge": 19, "your_extraction_plugin_nam": [19, 36], "attach": [19, 23, 29, 36, 41], "clearli": 19, "tutori": 19, "kubectl": [19, 36], "f": [19, 36, 41], "your_extraction_plugin_pod": [19, 36], "testpluginflitsit": 19, "embeddedextractionpluginflit": [19, 24], "testpath": [19, 24], "srcpath": [19, 24], "resultpath": [19, 24], "protect": [19, 24], "plugintotest": [19, 24, 42], "jpg": [19, 36], "hql_lite": [19, 36], "parseexcept": [19, 36], "hqllitehumanquerypars": [19, 36], "token": [19, 36], "visit": 20, "ddocker": 21, "buildarg": 21, "https_proxi": [21, 39], "8001": 21, "upload": [21, 37, 38, 39], "instal": [21, 35, 38, 39, 42], "machin": 21, "sock": 21, "export": 21, "docker_host": 21, "higher": [22, 37, 40], "basi": 22, "xmln": 22, "xsi": 22, "w3": 22, "2001": 22, "xmlschema": 22, "schemaloc": 22, "xsd": 22, "groupid": [22, 24], "artifactid": [22, 24], "set_the_sdk_version_her": 22, "choose_your_artifactid_her": 22, "set_the_plugin_version_her": 22, "mainclass": 22, "set_the_plugin_main_class_her": 22, "close": [23, 24, 31, 37, 41], "tracedata": 23, "asinputstream": 23, "safe": 23, "state": 23, "randomaccessdatainputstream": 23, "four": [23, 41], "telephon": [23, 41], "traceletproperti": 23, "demonstr": [23, 37, 41], "transorm": 23, "rawbyt": 23, "writer": [23, 41], "inherit": [23, 41], "tracesearch": [23, 28, 32, 41], "exampledef": 23, "searchresult": [23, 31, 32, 41], "asc": 23, "gettrac": 23, "slf4j": 23, "bind": 23, "illustr": 23, "begin": [23, 36], "logger": [23, 41], "turn": 23, "m": 23, "1234": 23, "loggerfactori": 23, "getlogg": 23, "anumb": 23, "log4j2": 23, "src": 23, "stdout": 23, "target": 23, "system_out": 23, "patternlayout": 23, "5p": 23, "yyyi": 23, "mm": 23, "dd": 23, "hh": 23, "ss": 23, "20t": 23, "32": 23, "32c": 23, "appenderref": 23, "pre": [23, 29], "yaml": 23, "json": [23, 36], "administr": [23, 37, 41], "exampleplugin": 23, "previewdata": 23, "png": [23, 41], "mime": [23, 41], "assum": [24, 42], "layer": 24, "minvenj": 24, "flitsutil": 24, "nio": 24, "mypluginit": 24, "hanskaton": 24, "fals": [24, 42], "outcom": [24, 42], "extractionserv": 24, "datatransformationsplugin": 24, "jupit": 24, "afteral": 24, "beforeal": 24, "remotetransformationpluginflitsit": 24, "_server": 24, "_client": 24, "init": 24, "localhost": [24, 36, 42], "getlisteningport": 24, "destruct": 24, "won": 24, "talk": 24, "testdata": [24, 42], "simpli": [24, 38, 42], "presum": 24, "data_typ": [27, 29, 41], "data_s": [27, 35], "baseextractionplugin": 28, "abc": [28, 29, 31, 34], "abstract": [28, 29, 31, 32], "plugin_info": [28, 35, 41, 42], "extractiontrac": [28, 29, 35, 41], "data_context": [28, 35, 41, 42], "metaextractionplugin": 28, "metaextractiontrac": [28, 29], "offer": [29, 30], "extractiontracebuild": 29, "key_or_upd": 29, "suppli": [29, 38, 42], "thrown": 29, "add_tracelet": [29, 35, 41], "add_transform": [29, 35, 41], "child_build": [29, 35, 41], "tracebuild": 29, "liter": 29, "rb": [29, 41], "wb": [29, 41], "bufferedwrit": 29, "represenst": 29, "enum": 30, "matur": [30, 35], "ready_for_test": 30, "production_readi": 30, "among": 30, "maximum_cpu": [30, 35, 41], "maximum_memori": [30, 35, 41], "equival": 30, "cloud": 30, "metal": 30, "intel": 30, "processor": 30, "fraction": 30, "ask": [30, 37, 44], "half": 30, "usabl": 30, "megabyt": 30, "webpage_url": [30, 35], "deferred_iter": 30, "short": 30, "nly": 30, "kwd": 31, "treat": 31, "batch": 31, "first_100": 31, "100": 31, "process_batch": 31, "takeon": [31, 41], "total_result": 31, "exhaust": [31, 41], "num": 31, "zero": 31, "keep": [31, 41], "emailaddress": 33, "notreal": 33, "com": [33, 37], "firstnam": 33, "piet": 33, "anotheremail": 33, "helper": 34, "add_rang": [34, 35, 41], "param": 34, "outsid": [35, 36, 43], "serve_plugin": [35, 41, 42], "handi": 35, "plugin_fil": [35, 39], "docker_file_directori": [35, 39], "docker_image_nam": [35, 39], "docker_arg": [35, 39], "strongli": 35, "significantli": 35, "carefulli": 35, "chatplugin": [35, 39], "unnam": 35, "def": [35, 41, 42], "anymor": 35, "instanti": 35, "2048": 35, "4096": 35, "john": 35, "statu": 35, "onlin": 35, "from_sequ": 35, "width": 35, "height": 35, "tester": 35, "holm": 35, "extraction_plugin": [35, 38, 42], "extraction_context": 35, "hansken_extraction_plugin": [35, 38, 41, 42], "maturity_level": 35, "get_first_byt": 35, "extraction_trac": 35, "validate_update_argu": 35, "implicitli": 35, "studio": 36, "microsoft": [36, 37], "dap": 36, "standardis": 36, "setuptool": 36, "install_requir": 36, "wait_for_cli": 36, "5678": 36, "flag": 36, "firewal": 36, "world": 36, "launch": 36, "minim": 36, "pathmap": 36, "localroot": 36, "workspacefold": 36, "remoteroot": 36, "your_plugin": 36, "fulli": [37, 44], "comfort": 37, "complet": 37, "pip": 37, "sudo": 37, "apt": 37, "python3": [37, 38], "jdk": 37, "pip3": 37, "continu": 37, "verif": 37, "prompt": [37, 38], "button": [37, 38], "cmd": 37, "enter": [37, 38], "yellow": 37, "librari": [37, 41], "23": 37, "openjdk": 37, "edit": 37, "bin": 37, "javajava": 37, "11jdk": 37, "4bin": 37, "press": 37, "ok": 37, "complic": 37, "gom": 37, "17": 37, "certif": 37, "aid": [37, 38], "jetbrain": 37, "excel": 37, "rapidli": 37, "netherlandsforensicinstitut": 37, "screenshot": 37, "unzip": 37, "popup": 37, "appear": 37, "readm": 37, "md": 37, "suit": 37, "alt": 37, "f12": 37, "minut": 37, "patient": 37, "summari": 37, "py38": 37, "succeed": 37, "congratul": 37, "cool": 37, "stuff": 37, "quick": 37, "prototyp": 37, "isn": 37, "netherland": 38, "institut": 38, "cycl": 38, "slower": 38, "run_with_hansken": 38, "extraction_plugin_runn": 38, "my_plugin": [38, 42], "__name__": [38, 41, 42], "__main__": [38, 42], "hansken_project_id": 38, "your_gatekeeper_url": 38, "your_keystore_url": 38, "keystor": 38, "expertui": 38, "bottom": 38, "respect": 38, "l": 38, "usernam": 38, "password": 38, "vari": 38, "03": 38, "59": 38, "45": 38, "344248": 38, "0000": 38, "auth": 38, "idp": 38, "soap": 38, "344450": 38, "testaccount": 38, "423245": 38, "acknowledg": 38, "custom": 38, "53": 38, "799668": 38, "keycloak": 38, "saml": 38, "805538": 38, "pluginrunn": 38, "859299": 38, "post": 38, "54": 38, "240290": 38, "54197e67": 38, "8135": 38, "40c3": 38, "93f1": 38, "3d73a5552693": 38, "240753": 38, "ocrimag": 38, "http_proxi": 39, "dockerfil": [39, 41], "period": 39, "dash": 39, "omit": 40, "file_nam": 41, "chatconvers": 41, "demoapp": 41, "hold": 41, "timezon": 41, "utc": 41, "modifiedon": 41, "fromtimestamp": 41, "1630510809": 41, "tz": 41, "misc": 41, "miscellan": 41, "anynam": 41, "childtrac": 41, "ann": 41, "grandchild_build": 41, "grandchild": 41, "quantiti": 41, "smaller": 41, "another_str": 41, "in_fil": 41, "deferredplugin": 41, "meth": 41, "search_result": 41, "logbook": 41, "top": 41, "pretti": 41, "straight": 41, "my_vari": 41, "vv": 41, "vvv": [41, 42], "x00": 41, "xff": 41, "appropri": 42, "ini": 42, "littl": 42, "feed": 42, "henc": 42, "cd": 42, "chat_plugin": 42, "rel": 42, "my": 42, "spin": 42, "_test_validate_standalon": 42, "easier": 42, "overview": 42, "worri": 43, "conform": 43, "spec": 43, "major": 43, "extractionpluginservic": 43, "entrypoint": 43, "unsur": 43, "max_cpu": 43, "milicpu": 43, "max_mem": 43, "kit": 44, "introduct": 44, "technologi": 44, "stabl": 44, "frequent": 44, "jorgb": 44}, "objects": {"hansken_extraction_plugin": [[26, 0, 0, "-", "api"]], "hansken_extraction_plugin.api": [[27, 0, 0, "-", "data_context"], [28, 0, 0, "-", "extraction_plugin"], [29, 0, 0, "-", "extraction_trace"], [30, 0, 0, "-", "plugin_info"], [31, 0, 0, "-", "search_result"], [32, 0, 0, "-", "trace_searcher"], [33, 0, 0, "-", "tracelet"], [34, 0, 0, "-", "transformation"]], "hansken_extraction_plugin.api.data_context": [[27, 1, 1, "", "DataContext"]], "hansken_extraction_plugin.api.data_context.DataContext": [[27, 2, 1, "", "data_size"], [27, 2, 1, "", "data_type"]], "hansken_extraction_plugin.api.extraction_plugin": [[28, 1, 1, "", "BaseExtractionPlugin"], [28, 1, 1, "", "DeferredExtractionPlugin"], [28, 1, 1, "", "ExtractionPlugin"], [28, 1, 1, "", "MetaExtractionPlugin"]], "hansken_extraction_plugin.api.extraction_plugin.BaseExtractionPlugin": [[28, 3, 1, "", "plugin_info"]], "hansken_extraction_plugin.api.extraction_plugin.DeferredExtractionPlugin": [[28, 3, 1, "", "process"]], "hansken_extraction_plugin.api.extraction_plugin.ExtractionPlugin": [[28, 3, 1, "", "process"]], "hansken_extraction_plugin.api.extraction_plugin.MetaExtractionPlugin": [[28, 3, 1, "", "process"]], "hansken_extraction_plugin.api.extraction_trace": [[29, 1, 1, "", "ExtractionTrace"], [29, 1, 1, "", "ExtractionTraceBuilder"], [29, 1, 1, "", "MetaExtractionTrace"], [29, 1, 1, "", "SearchTrace"], [29, 1, 1, "", "Trace"]], "hansken_extraction_plugin.api.extraction_trace.ExtractionTrace": [[29, 3, 1, "", "open"]], "hansken_extraction_plugin.api.extraction_trace.ExtractionTraceBuilder": [[29, 3, 1, "", "add_data"], [29, 3, 1, "", "add_tracelet"], [29, 3, 1, "", "add_transformation"], [29, 3, 1, "", "build"], [29, 3, 1, "", "child_builder"], [29, 3, 1, "", "open"], [29, 3, 1, "", "update"]], "hansken_extraction_plugin.api.extraction_trace.MetaExtractionTrace": [[29, 3, 1, "", "add_tracelet"], [29, 3, 1, "", "add_transformation"], [29, 3, 1, "", "child_builder"], [29, 3, 1, "", "update"]], "hansken_extraction_plugin.api.extraction_trace.SearchTrace": [[29, 3, 1, "", "open"]], "hansken_extraction_plugin.api.extraction_trace.Trace": [[29, 3, 1, "", "get"]], "hansken_extraction_plugin.api.plugin_info": [[30, 1, 1, "", "Author"], [30, 1, 1, "", "MaturityLevel"], [30, 1, 1, "", "PluginId"], [30, 1, 1, "", "PluginInfo"], [30, 1, 1, "", "PluginResources"]], "hansken_extraction_plugin.api.plugin_info.Author": [[30, 2, 1, "", "email"], [30, 2, 1, "", "name"], [30, 2, 1, "", "organisation"]], "hansken_extraction_plugin.api.plugin_info.MaturityLevel": [[30, 2, 1, "", "PRODUCTION_READY"], [30, 2, 1, "", "PROOF_OF_CONCEPT"], [30, 2, 1, "", "READY_FOR_TEST"]], "hansken_extraction_plugin.api.plugin_info.PluginId": [[30, 2, 1, "", "category"], [30, 2, 1, "", "domain"], [30, 2, 1, "", "name"]], "hansken_extraction_plugin.api.plugin_info.PluginInfo": [[30, 2, 1, "", "author"], [30, 2, 1, "", "deferred_iterations"], [30, 2, 1, "", "description"], [30, 2, 1, "", "id"], [30, 2, 1, "", "license"], [30, 2, 1, "", "matcher"], [30, 2, 1, "", "maturity"], [30, 2, 1, "", "resources"], [30, 2, 1, "", "version"], [30, 2, 1, "", "webpage_url"]], "hansken_extraction_plugin.api.plugin_info.PluginResources": [[30, 2, 1, "", "maximum_cpu"], [30, 2, 1, "", "maximum_memory"]], "hansken_extraction_plugin.api.search_result": [[31, 1, 1, "", "SearchResult"]], "hansken_extraction_plugin.api.search_result.SearchResult": [[31, 3, 1, "", "close"], [31, 3, 1, "", "take"], [31, 3, 1, "", "takeone"], [31, 3, 1, "", "total_results"]], "hansken_extraction_plugin.api.trace_searcher": [[32, 1, 1, "", "TraceSearcher"]], "hansken_extraction_plugin.api.trace_searcher.TraceSearcher": [[32, 3, 1, "", "search"]], "hansken_extraction_plugin.api.tracelet": [[33, 1, 1, "", "Tracelet"]], "hansken_extraction_plugin.api.transformation": [[34, 1, 1, "", "Range"], [34, 1, 1, "", "RangedTransformation"], [34, 1, 1, "", "Transformation"]], "hansken_extraction_plugin.api.transformation.Range": [[34, 2, 1, "", "length"], [34, 2, 1, "", "offset"]], "hansken_extraction_plugin.api.transformation.RangedTransformation": [[34, 1, 1, "", "Builder"], [34, 3, 1, "", "builder"]], "hansken_extraction_plugin.api.transformation.RangedTransformation.Builder": [[34, 3, 1, "", "add_range"], [34, 3, 1, "", "build"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:attribute", "3": "py:method"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "attribute", "Python attribute"], "3": ["py", "method", "Python method"]}, "titleterms": {"changelog": [0, 18, 35], "releas": 0, "0": [0, 18, 35], "7": [0, 18, 35], "6": [0, 18, 35], "3": [0, 18, 35], "2": [0, 18, 35], "1": [0, 18, 35], "5": [0, 18, 35], "4": [0, 18, 35], "14": 0, "13": [0, 18, 35], "12": 0, "11": 0, "10": 0, "9": 0, "8": 0, "contact": 1, "gener": 2, "concept": 2, "content": [2, 17, 25, 44], "debug": [3, 19, 36], "local": [3, 19, 36], "hansken": [3, 4, 6, 7, 13, 15, 38, 44], "all": 3, "One": 3, "aio": 3, "tip": [3, 42], "note": 3, "anatomi": 4, "plugin": [4, 6, 7, 8, 10, 11, 12, 13, 15, 16, 19, 23, 36, 37, 38, 41, 43, 44], "The": [4, 12], "itself": 4, "method": 4, "plugininfo": 4, "process": 4, "execut": 4, "discoveri": 4, "start": [4, 6, 37, 42], "an": [4, 6, 7, 19, 36, 37], "extract": [4, 6, 11, 12, 13, 15, 19, 23, 36, 37, 38, 43, 44], "finish": 4, "data": [5, 7, 12, 13, 23, 41], "transform": [5, 23, 34, 41], "see": 5, "also": 5, "build": [6, 19, 35, 36], "packag": [6, 21, 39, 43], "upload": 6, "refresh": 6, "tool": [6, 19], "list": 6, "enabl": 6, "hql": [7, 19, 36], "lite": 7, "overview": 7, "how": [7, 12, 15, 19, 36, 38], "doe": 7, "work": 7, "what": [7, 15], "can_this_tool_process_the_provided_trac": 7, "do": 7, "matcher": [7, 12], "rescu": 7, "i": [7, 15], "why": [7, 15], "just": 7, "us": [7, 15, 24, 42], "syntax": 7, "when": 7, "write": 7, "pdfplugin": 7, "exampl": [7, 10, 14, 24], "my": [7, 15], "expect": [7, 42], "input": [7, 12], "can": [7, 15], "describ": 7, "inform": 7, "provid": 7, "match": 7, "extens": 7, "mime": 7, "type": [7, 11, 13], "size": 7, "properti": [7, 13, 41], "set": [7, 9, 19, 36, 37], "exclud": 7, "certain": 7, "path": 7, "specif": [7, 19, 43], "datastream": 7, "anti": 7, "pattern": 7, "precis": 7, "should": 7, "isol": 8, "user": 8, "system": [8, 23, 41], "call": 8, "network": 8, "kubernet": [9, 19, 36], "autosc": 9, "resourcemanag": 9, "find": 9, "right": 9, "name": 10, "convent": 10, "identifi": 10, "standard": 11, "meta": 11, "defer": [11, 12, 23, 41], "test": [12, 24, 42], "framework": [12, 24, 42], "creat": [12, 15, 38], "basic": 12, "directori": 12, "structur": 12, "trace": [12, 13, 23, 41], "format": 12, "json": 12, "result": [12, 42], "except": 12, "leav": 12, "out": 12, "messag": 12, "startswith": 12, "partial": 12, "containsinord": 12, "your": [12, 15, 37, 38, 42], "java": [12, 17, 18, 19, 23, 24], "python": [12, 25, 35, 36, 38, 41, 42], "model": 13, "intrins": 13, "stream": [13, 41], "child": [13, 41], "vector": 13, "tracelet": [13, 23, 33, 41], "sdk": [13, 15, 16, 44], "frequent": 15, "ask": 15, "question": 15, "access": 15, "develop": [15, 16, 44], "commun": 15, "program": 15, "languag": 15, "ar": 15, "support": 15, "Will": 15, "you": 15, "foobar": 15, "reus": 15, "modifi": 15, "someon": 15, "els": 15, "wrote": 15, "legal": 15, "implic": 15, "own": 15, "": 15, "safe": 15, "embed": [15, 24], "perform": 15, "reason": 15, "introduct": 16, "softwar": [16, 37], "kit": 16, "step": [16, 37], "api": [18, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35], "log": [19, 23, 36, 41], "docker": [19, 36, 37, 42], "imag": [19, 36, 42], "run": [19, 38], "option": 19, "breakpoint": [19, 36], "code": [19, 23, 36, 41], "javadoc": 20, "prerequisit": [22, 24, 40], "snippet": [23, 41], "randomaccessdata": 23, "inputstream": 23, "ad": [23, 41], "blob": [23, 41], "specifi": [23, 41], "resourc": [23, 41], "usag": 23, "custom": 23, "experiment": [23, 41], "featur": [23, 41], "preview": [23, 41], "versu": 24, "remot": 24, "document": [25, 44], "hansken_extraction_plugin": [26, 27, 28, 29, 30, 31, 32, 33, 34], "data_context": 27, "extraction_plugin": 28, "extraction_trac": 29, "plugin_info": 30, "search_result": 31, "trace_search": 32, "pipelin": 35, "chang": 35, "instal": [36, 37], "debugpi": 36, "configur": 36, "connect": 36, "contain": 36, "get": 37, "requir": 37, "ubuntu": 37, "window": 37, "up": 37, "id": [37, 42], "pycharm": 37, "download": 37, "templat": 37, "empti": 37, "import": 37, "skeleton": 37, "verifi": 37, "full": 37, "setup": 37, "next": 37, "py": 38, "standalon": [38, 42], "runner": 38, "file": 38, "prepar": 38, "command": 38, "compat": 38, "date": 41, "categori": 41, "extra": 41, "advanc": 42, "regener": 42, "manual": 42, "help": 42, "protocol": 43, "quick": 44, "link": 44, "welcom": 44}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.todo": 2, "sphinx.ext.viewcode": 1, "sphinx": 57}, "alltitles": {"Changelog": [[0, "changelog"]], "Release-0.7.0\n": [[0, "release-version"]], "Release-0.6.3": [[0, "release-0-6-3"]], "Release-0.6.2": [[0, "release-0-6-2"]], "Release-0.6.1": [[0, "release-0-6-1"]], "Release-0.6.0": [[0, "release-0-6-0"]], "Release-0.5.1": [[0, "release-0-5-1"]], "Release-0.5.0": [[0, "release-0-5-0"]], "Release-0.4.14": [[0, "release-0-4-14"]], "Release-0.4.13": [[0, "release-0-4-13"]], "Release-0.4.12": [[0, "release-0-4-12"]], "Release-0.4.11": [[0, "release-0-4-11"]], "Release-0.4.10": [[0, "release-0-4-10"]], "Release-0.4.9": [[0, "release-0-4-9"]], "Release-0.4.8": [[0, "release-0-4-8"]], "Release-0.4.7": [[0, "release-0-4-7"]], "Release-0.4.6": [[0, "release-0-4-6"]], "Release-0.4.5": [[0, "release-0-4-5"]], "Release-0.4.4": [[0, "release-0-4-4"]], "Release-0.4.3": [[0, "release-0-4-3"]], "Release-0.4.2": [[0, "release-0-4-2"]], "Release-0.4.1": [[0, "release-0-4-1"]], "Release-0.4.0": [[0, "release-0-4-0"]], "Release-0.3.0": [[0, "release-0-3-0"]], "Release-0.2.0": [[0, "release-0-2-0"]], "Release-0.1.8": [[0, "release-0-1-8"]], "Release-0.1.7": [[0, "release-0-1-7"]], "Release-0.1.6": [[0, "release-0-1-6"]], "Release-0.1.5": [[0, "release-0-1-5"]], "Release-0.1.4": [[0, "release-0-1-4"]], "Release-0.1.3": [[0, "release-0-1-3"]], "Release-0.1.2": [[0, "release-0-1-2"]], "Release-0.1.1": [[0, "release-0-1-1"]], "Release-0.1.0": [[0, "release-0-1-0"]], "Contact": [[1, "contact"]], "General concepts": [[2, "general-concepts"]], "Contents:": [[2, null], [17, null], [25, null], [44, null]], "Debugging locally with Hansken All in One (AIO)": [[3, "debugging-locally-with-hansken-all-in-one-aio"]], "Tips/notes:": [[3, "tips-notes"]], "Anatomy of a plugin": [[4, "anatomy-of-a-plugin"]], "The plugin itself": [[4, "the-plugin-itself"]], "The method pluginInfo()": [[4, "the-method-plugininfo"]], "The method process()": [[4, "the-method-process"]], "The execution in Hansken": [[4, "the-execution-in-hansken"]], "Plugin discovery": [[4, "plugin-discovery"]], "Starting an extraction": [[4, "starting-an-extraction"]], "Extracting": [[4, "extracting"]], "Finishing an extraction": [[4, "finishing-an-extraction"]], "Data Transformations": [[5, "data-transformations"], [23, "data-transformations"], [41, "data-transformations"]], "See also": [[5, "see-also"]], "Hansken Extraction Plugins": [[6, "hansken-extraction-plugins"]], "Building a plugin": [[6, "building-a-plugin"]], "Package the plugin": [[6, "package-the-plugin"]], "Upload the plugin to Hansken": [[6, "upload-the-plugin-to-hansken"]], "Refresh the Hansken tools list": [[6, "refresh-the-hansken-tools-list"]], "Start an extraction with the plugin enabled": [[6, "start-an-extraction-with-the-plugin-enabled"]], "HQL-Lite": [[7, "hql-lite"]], "Overview": [[7, "overview"]], "How does Hansken work?": [[7, "how-does-hansken-work"]], "What does can_this_tool_process_the_provided_trace() do?": [[7, "what-does-can-this-tool-process-the-provided-trace-do"]], "Matchers to the rescue": [[7, "matchers-to-the-rescue"]], "What is HQL-Lite?": [[7, "what-is-hql-lite"]], "Why not just use HQL for plugins?": [[7, "why-not-just-use-hql-for-plugins"]], "HQL-Lite syntax": [[7, "hql-lite-syntax"]], "$data matchers": [[7, "data-matchers"]], "When is it useful to use a $data matcher?": [[7, "when-is-it-useful-to-use-a-data-matcher"]], "How to write a matcher?": [[7, "how-to-write-a-matcher"]], "PdfPlugin example": [[7, "pdfplugin-example"]], "What does my plugin expect as input?": [[7, "what-does-my-plugin-expect-as-input"]], "How can I describe that input with the information Hansken provides?": [[7, "how-can-i-describe-that-input-with-the-information-hansken-provides"]], "Match on extension": [[7, "match-on-extension"]], "Match on mime-type": [[7, "match-on-mime-type"]], "Match on data size": [[7, "match-on-data-size"]], "Match if \u2018property is set\u2019": [[7, "match-if-property-is-set"]], "Match on excluding a certain path": [[7, "match-on-excluding-a-certain-path"]], "Match on specific datastream type, an anti-pattern": [[7, "match-on-specific-datastream-type-an-anti-pattern"]], "How precise should a matcher be?": [[7, "how-precise-should-a-matcher-be"]], "Plugin isolation": [[8, "plugin-isolation"]], "User isolation": [[8, "user-isolation"]], "System calls": [[8, "system-calls"]], "Network isolation": [[8, "network-isolation"]], "Kubernetes, Autoscaling, Resourcemanagement": [[9, "kubernetes-autoscaling-resourcemanagement"]], "Autoscaling": [[9, "autoscaling"]], "Finding the right settings": [[9, "finding-the-right-settings"]], "Plugin naming convention": [[10, "plugin-naming-convention"]], "Plugin identifier": [[10, "plugin-identifier"]], "Examples": [[10, "examples"], [14, "examples"]], "Extraction plugin types": [[11, "extraction-plugin-types"]], "Standard Extraction Plugins": [[11, "standard-extraction-plugins"]], "Meta Extraction Plugins": [[11, "meta-extraction-plugins"]], "Deferred Extraction Plugins": [[11, "deferred-extraction-plugins"], [23, "deferred-extraction-plugins"]], "Test framework": [[12, "test-framework"]], "Creating test data": [[12, "creating-test-data"]], "Basic test data directory structure": [[12, "basic-test-data-directory-structure"]], "Test data structure for deferred extraction plugins": [[12, "test-data-structure-for-deferred-extraction-plugins"]], "Trace format": [[12, "trace-format"]], "Input trace JSON format": [[12, "input-trace-json-format"]], "Result trace JSON format": [[12, "result-trace-json-format"]], "Testing exceptions": [[12, "testing-exceptions"]], "Leaving out the message": [[12, "leaving-out-the-message"]], "The startsWith partial result matcher": [[12, "the-startswith-partial-result-matcher"]], "The containsInOrder partial result matcher": [[12, "the-containsinorder-partial-result-matcher"]], "How to test your plugin": [[12, "how-to-test-your-plugin"]], "Java": [[12, "java"], [17, "java"]], "Python": [[12, "python"], [25, "python"]], "Traces & Trace model": [[13, "traces-trace-model"]], "Traces": [[13, "traces"]], "Types and Properties": [[13, "types-and-properties"]], "Intrinsic properties": [[13, "intrinsic-properties"]], "Data streams": [[13, "data-streams"]], "Child traces": [[13, "child-traces"]], "Trace property types": [[13, "trace-property-types"]], "Vector": [[13, "vector"]], "Tracelets": [[13, "tracelets"]], "Hansken trace model": [[13, "hansken-trace-model"]], "Trace model and the extraction plugin SDK": [[13, "trace-model-and-the-extraction-plugin-sdk"]], "Frequently Asked Questions": [[15, "frequently-asked-questions"]], "How can I access Hansken developer community": [[15, "how-can-i-access-hansken-developer-community"]], "Why use Extraction Plugins?": [[15, "why-use-extraction-plugins"]], "What programming languages are supported?": [[15, "what-programming-languages-are-supported"]], "Will you support language foobar?": [[15, "will-you-support-language-foobar"]], "Can I reuse or modify the Extraction Plugins SDK?": [[15, "can-i-reuse-or-modify-the-extraction-plugins-sdk"]], "Can I use a plugin that someone else wrote?": [[15, "can-i-use-a-plugin-that-someone-else-wrote"]], "What are the legal implications of creating your own Extraction Plugin(s)?": [[15, "what-are-the-legal-implications-of-creating-your-own-extraction-plugin-s"]], "How safe are Extraction Plugins?": [[15, "how-safe-are-extraction-plugins"]], "Can my Extraction Plugin be embedded into Hansken for performance reasons?": [[15, "can-my-extraction-plugin-be-embedded-into-hansken-for-performance-reasons"]], "Introduction": [[16, "introduction"]], "Software Development Kit (SDK)": [[16, "software-development-kit-sdk"]], "Development steps of a plugin": [[16, "development-steps-of-a-plugin"]], "Java API Changelog": [[18, "java-api-changelog"]], "0.7.0\n": [[18, "version"], [35, "version"]], "0.6.1": [[18, "id1"], [35, "id1"]], "0.6.0": [[18, "id2"], [35, "id2"]], "0.5.0": [[18, "id3"], [35, "id4"]], "0.4.13": [[18, "id4"], [35, "id5"]], "0.4.7": [[18, "id5"], [35, "id6"]], "0.4.6": [[18, "id6"], [35, "id7"]], "0.4.0": [[18, "id7"], [35, "id8"]], "0.3.0": [[18, "id8"], [35, "id9"]], "0.2.0": [[18, "id9"], [35, "id10"]], "How to debug an Extraction Plugin": [[19, "how-to-debug-an-extraction-plugin"], [36, "how-to-debug-an-extraction-plugin"]], "Locally": [[19, "locally"], [36, "locally"]], "Logging": [[19, "logging"], [23, "logging"], [36, "logging"], [41, "logging"]], "Locally with Docker": [[19, "locally-with-docker"], [36, "locally-with-docker"]], "Build a Docker image": [[19, "build-a-docker-image"], [36, "build-a-docker-image"]], "Run the Docker image with specific Java tool options": [[19, "run-the-docker-image-with-specific-java-tool-options"]], "Setting breakpoints in the code": [[19, "setting-breakpoints-in-the-code"], [36, "setting-breakpoints-in-the-code"]], "Logging in Docker": [[19, "logging-in-docker"], [36, "logging-in-docker"]], "Kubernetes": [[19, "kubernetes"], [36, "kubernetes"]], "Logging in Kubernetes": [[19, "logging-in-kubernetes"], [36, "logging-in-kubernetes"]], "Debug HQL": [[19, "debug-hql"], [36, "debug-hql"]], "Javadoc": [[20, "javadoc"]], "Packaging": [[21, "packaging"], [39, "packaging"], [43, "packaging"]], "Prerequisites": [[22, "prerequisites"], [24, "prerequisites"], [40, "prerequisites"]], "Java code snippets": [[23, "java-code-snippets"]], "RandomAccessData as InputStream": [[23, "randomaccessdata-as-inputstream"]], "Adding tracelets": [[23, "adding-tracelets"], [41, "adding-tracelets"]], "Adding data to a trace": [[23, "adding-data-to-a-trace"], [41, "adding-data-to-a-trace"]], "Blobs": [[23, "blobs"], [41, "blobs"]], "Specifying system resources": [[23, "specifying-system-resources"], [41, "specifying-system-resources"]], "Usage": [[23, "usage"]], "Customize logging": [[23, "customize-logging"]], "[EXPERIMENTAL FEATURE] Adding previews to a trace": [[23, "experimental-feature-adding-previews-to-a-trace"], [41, "experimental-feature-adding-previews-to-a-trace"]], "Using the Test Framework in Java": [[24, "using-the-test-framework-in-java"]], "Embedded Testing versus Remote Testing": [[24, "embedded-testing-versus-remote-testing"]], "Embedded Testing example": [[24, "embedded-testing-example"]], "Remote Testing example": [[24, "remote-testing-example"]], "API Documentation": [[25, "api-documentation"]], "hansken_extraction_plugin.api": [[26, "module-hansken_extraction_plugin.api"]], "hansken_extraction_plugin.api.data_context": [[27, "module-hansken_extraction_plugin.api.data_context"]], "hansken_extraction_plugin.api.extraction_plugin": [[28, "module-hansken_extraction_plugin.api.extraction_plugin"]], "hansken_extraction_plugin.api.extraction_trace": [[29, "module-hansken_extraction_plugin.api.extraction_trace"]], "hansken_extraction_plugin.api.plugin_info": [[30, "module-hansken_extraction_plugin.api.plugin_info"]], "hansken_extraction_plugin.api.search_result": [[31, "module-hansken_extraction_plugin.api.search_result"]], "hansken_extraction_plugin.api.trace_searcher": [[32, "module-hansken_extraction_plugin.api.trace_searcher"]], "hansken_extraction_plugin.api.tracelet": [[33, "module-hansken_extraction_plugin.api.tracelet"]], "hansken_extraction_plugin.api.transformation": [[34, "module-hansken_extraction_plugin.api.transformation"]], "Python API Changelog": [[35, "python-api-changelog"]], "Build pipeline change": [[35, "build-pipeline-change"]], "API changes": [[35, "api-changes"]], "0.5.1": [[35, "id3"]], "Install debugpy": [[36, "install-debugpy"]], "Configuring debugpy in Python": [[36, "configuring-debugpy-in-python"]], "Configuring the connection to the Docker container": [[36, "configuring-the-connection-to-the-docker-container"]], "Getting started": [[37, "getting-started"]], "Install required software on Ubuntu": [[37, "install-required-software-on-ubuntu"]], "Install required software on Windows.": [[37, "install-required-software-on-windows"]], "Install Docker (Ubuntu, Windows)": [[37, "install-docker-ubuntu-windows"]], "Set up your IDE: PyCharm": [[37, "set-up-your-ide-pycharm"]], "Download an extraction plugin template (empty plugin)": [[37, "download-an-extraction-plugin-template-empty-plugin"]], "Import the Extraction Plugins Skeleton in PyCharm": [[37, "import-the-extraction-plugins-skeleton-in-pycharm"]], "Verify full setup": [[37, "verify-full-setup"]], "Next steps": [[37, "next-steps"]], "Run plugins with Hansken.py": [[38, "run-plugins-with-hansken-py"]], "How to run python extraction plugins standalone with Hansken.py": [[38, "how-to-run-python-extraction-plugins-standalone-with-hansken-py"]], "Create a runner file": [[38, "create-a-runner-file"]], "Preparing for the command": [[38, "preparing-for-the-command"]], "Running your plugin with Hansken.py": [[38, "running-your-plugin-with-hansken-py"]], "Compatibility": [[38, "compatibility"]], "Python code snippets": [[41, "python-code-snippets"]], "Adding properties to a trace": [[41, "adding-properties-to-a-trace"]], "Date properties": [[41, "date-properties"]], "Category for extra properties": [[41, "category-for-extra-properties"]], "Adding child traces to a trace": [[41, "adding-child-traces-to-a-trace"]], "Streaming data": [[41, "streaming-data"]], "Deferred Plugins": [[41, "deferred-plugins"]], "Advanced use of the Test Framework in Python": [[42, "advanced-use-of-the-test-framework-in-python"]], "Regenerate expected test results": [[42, "regenerate-expected-test-results"]], "Standalone testing": [[42, "standalone-testing"]], "Testing with a Docker image": [[42, "testing-with-a-docker-image"]], "Manual testing": [[42, "manual-testing"]], "Tip: Start tests in your IDE": [[42, "tip-start-tests-in-your-ide"]], "Help": [[42, "help"]], "Extraction Plugin specifications": [[43, "extraction-plugin-specifications"]], "Plugin protocol": [[43, "plugin-protocol"]], "Hansken extraction plugin SDK documentation for plugin developers": [[44, "hansken-extraction-plugin-sdk-documentation-for-plugin-developers"]], "Quick links": [[44, "quick-links"]], "Welcome": [[44, "welcome"]]}, "indexentries": {"hansken_extraction_plugin.api": [[26, "module-hansken_extraction_plugin.api"]], "module": [[26, "module-hansken_extraction_plugin.api"], [27, "module-hansken_extraction_plugin.api.data_context"], [28, "module-hansken_extraction_plugin.api.extraction_plugin"], [29, "module-hansken_extraction_plugin.api.extraction_trace"], [30, "module-hansken_extraction_plugin.api.plugin_info"], [31, "module-hansken_extraction_plugin.api.search_result"], [32, "module-hansken_extraction_plugin.api.trace_searcher"], [33, "module-hansken_extraction_plugin.api.tracelet"], [34, "module-hansken_extraction_plugin.api.transformation"]], "datacontext (class in hansken_extraction_plugin.api.data_context)": [[27, "hansken_extraction_plugin.api.data_context.DataContext"]], "data_size (datacontext attribute)": [[27, "hansken_extraction_plugin.api.data_context.DataContext.data_size"]], "data_type (datacontext attribute)": [[27, "hansken_extraction_plugin.api.data_context.DataContext.data_type"]], "hansken_extraction_plugin.api.data_context": [[27, "module-hansken_extraction_plugin.api.data_context"]], "baseextractionplugin (class in hansken_extraction_plugin.api.extraction_plugin)": [[28, "hansken_extraction_plugin.api.extraction_plugin.BaseExtractionPlugin"]], "deferredextractionplugin (class in hansken_extraction_plugin.api.extraction_plugin)": [[28, "hansken_extraction_plugin.api.extraction_plugin.DeferredExtractionPlugin"]], "extractionplugin (class in hansken_extraction_plugin.api.extraction_plugin)": [[28, "hansken_extraction_plugin.api.extraction_plugin.ExtractionPlugin"]], "metaextractionplugin (class in hansken_extraction_plugin.api.extraction_plugin)": [[28, "hansken_extraction_plugin.api.extraction_plugin.MetaExtractionPlugin"]], "hansken_extraction_plugin.api.extraction_plugin": [[28, "module-hansken_extraction_plugin.api.extraction_plugin"]], "plugin_info() (baseextractionplugin method)": [[28, "hansken_extraction_plugin.api.extraction_plugin.BaseExtractionPlugin.plugin_info"]], "process() (deferredextractionplugin method)": [[28, "hansken_extraction_plugin.api.extraction_plugin.DeferredExtractionPlugin.process"]], "process() (extractionplugin method)": [[28, "hansken_extraction_plugin.api.extraction_plugin.ExtractionPlugin.process"]], "process() (metaextractionplugin method)": [[28, "hansken_extraction_plugin.api.extraction_plugin.MetaExtractionPlugin.process"]], "extractiontrace (class in hansken_extraction_plugin.api.extraction_trace)": [[29, "hansken_extraction_plugin.api.extraction_trace.ExtractionTrace"]], "extractiontracebuilder (class in hansken_extraction_plugin.api.extraction_trace)": [[29, "hansken_extraction_plugin.api.extraction_trace.ExtractionTraceBuilder"]], "metaextractiontrace (class in hansken_extraction_plugin.api.extraction_trace)": [[29, "hansken_extraction_plugin.api.extraction_trace.MetaExtractionTrace"]], "searchtrace (class in hansken_extraction_plugin.api.extraction_trace)": [[29, "hansken_extraction_plugin.api.extraction_trace.SearchTrace"]], "trace (class in hansken_extraction_plugin.api.extraction_trace)": [[29, "hansken_extraction_plugin.api.extraction_trace.Trace"]], "add_data() (extractiontracebuilder method)": [[29, "hansken_extraction_plugin.api.extraction_trace.ExtractionTraceBuilder.add_data"]], "add_tracelet() (extractiontracebuilder method)": [[29, "hansken_extraction_plugin.api.extraction_trace.ExtractionTraceBuilder.add_tracelet"]], "add_tracelet() (metaextractiontrace method)": [[29, "hansken_extraction_plugin.api.extraction_trace.MetaExtractionTrace.add_tracelet"]], "add_transformation() (extractiontracebuilder method)": [[29, "hansken_extraction_plugin.api.extraction_trace.ExtractionTraceBuilder.add_transformation"]], "add_transformation() (metaextractiontrace method)": [[29, "hansken_extraction_plugin.api.extraction_trace.MetaExtractionTrace.add_transformation"]], "build() (extractiontracebuilder method)": [[29, "hansken_extraction_plugin.api.extraction_trace.ExtractionTraceBuilder.build"]], "child_builder() (extractiontracebuilder method)": [[29, "hansken_extraction_plugin.api.extraction_trace.ExtractionTraceBuilder.child_builder"]], "child_builder() (metaextractiontrace method)": [[29, "hansken_extraction_plugin.api.extraction_trace.MetaExtractionTrace.child_builder"]], "get() (trace method)": [[29, "hansken_extraction_plugin.api.extraction_trace.Trace.get"]], "hansken_extraction_plugin.api.extraction_trace": [[29, "module-hansken_extraction_plugin.api.extraction_trace"]], "open() (extractiontrace method)": [[29, "hansken_extraction_plugin.api.extraction_trace.ExtractionTrace.open"]], "open() (extractiontracebuilder method)": [[29, "hansken_extraction_plugin.api.extraction_trace.ExtractionTraceBuilder.open"]], "open() (searchtrace method)": [[29, "hansken_extraction_plugin.api.extraction_trace.SearchTrace.open"]], "update() (extractiontracebuilder method)": [[29, "hansken_extraction_plugin.api.extraction_trace.ExtractionTraceBuilder.update"]], "update() (metaextractiontrace method)": [[29, "hansken_extraction_plugin.api.extraction_trace.MetaExtractionTrace.update"]], "author (class in hansken_extraction_plugin.api.plugin_info)": [[30, "hansken_extraction_plugin.api.plugin_info.Author"]], "maturitylevel (class in hansken_extraction_plugin.api.plugin_info)": [[30, "hansken_extraction_plugin.api.plugin_info.MaturityLevel"]], "production_ready (maturitylevel attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.MaturityLevel.PRODUCTION_READY"]], "proof_of_concept (maturitylevel attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.MaturityLevel.PROOF_OF_CONCEPT"]], "pluginid (class in hansken_extraction_plugin.api.plugin_info)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginId"]], "plugininfo (class in hansken_extraction_plugin.api.plugin_info)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginInfo"]], "pluginresources (class in hansken_extraction_plugin.api.plugin_info)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginResources"]], "ready_for_test (maturitylevel attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.MaturityLevel.READY_FOR_TEST"]], "author (plugininfo attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginInfo.author"]], "category (pluginid attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginId.category"]], "deferred_iterations (plugininfo attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginInfo.deferred_iterations"]], "description (plugininfo attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginInfo.description"]], "domain (pluginid attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginId.domain"]], "email (author attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.Author.email"]], "hansken_extraction_plugin.api.plugin_info": [[30, "module-hansken_extraction_plugin.api.plugin_info"]], "id (plugininfo attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginInfo.id"]], "license (plugininfo attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginInfo.license"]], "matcher (plugininfo attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginInfo.matcher"]], "maturity (plugininfo attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginInfo.maturity"]], "maximum_cpu (pluginresources attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginResources.maximum_cpu"]], "maximum_memory (pluginresources attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginResources.maximum_memory"]], "name (author attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.Author.name"]], "name (pluginid attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginId.name"]], "organisation (author attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.Author.organisation"]], "resources (plugininfo attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginInfo.resources"]], "version (plugininfo attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginInfo.version"]], "webpage_url (plugininfo attribute)": [[30, "hansken_extraction_plugin.api.plugin_info.PluginInfo.webpage_url"]], "searchresult (class in hansken_extraction_plugin.api.search_result)": [[31, "hansken_extraction_plugin.api.search_result.SearchResult"]], "close() (searchresult method)": [[31, "hansken_extraction_plugin.api.search_result.SearchResult.close"]], "hansken_extraction_plugin.api.search_result": [[31, "module-hansken_extraction_plugin.api.search_result"]], "take() (searchresult method)": [[31, "hansken_extraction_plugin.api.search_result.SearchResult.take"]], "takeone() (searchresult method)": [[31, "hansken_extraction_plugin.api.search_result.SearchResult.takeone"]], "total_results() (searchresult method)": [[31, "hansken_extraction_plugin.api.search_result.SearchResult.total_results"]], "tracesearcher (class in hansken_extraction_plugin.api.trace_searcher)": [[32, "hansken_extraction_plugin.api.trace_searcher.TraceSearcher"]], "hansken_extraction_plugin.api.trace_searcher": [[32, "module-hansken_extraction_plugin.api.trace_searcher"]], "search() (tracesearcher method)": [[32, "hansken_extraction_plugin.api.trace_searcher.TraceSearcher.search"]], "tracelet (class in hansken_extraction_plugin.api.tracelet)": [[33, "hansken_extraction_plugin.api.tracelet.Tracelet"]], "hansken_extraction_plugin.api.tracelet": [[33, "module-hansken_extraction_plugin.api.tracelet"]], "range (class in hansken_extraction_plugin.api.transformation)": [[34, "hansken_extraction_plugin.api.transformation.Range"]], "rangedtransformation (class in hansken_extraction_plugin.api.transformation)": [[34, "hansken_extraction_plugin.api.transformation.RangedTransformation"]], "rangedtransformation.builder (class in hansken_extraction_plugin.api.transformation)": [[34, "hansken_extraction_plugin.api.transformation.RangedTransformation.Builder"]], "transformation (class in hansken_extraction_plugin.api.transformation)": [[34, "hansken_extraction_plugin.api.transformation.Transformation"]], "add_range() (rangedtransformation.builder method)": [[34, "hansken_extraction_plugin.api.transformation.RangedTransformation.Builder.add_range"]], "build() (rangedtransformation.builder method)": [[34, "hansken_extraction_plugin.api.transformation.RangedTransformation.Builder.build"]], "builder() (rangedtransformation static method)": [[34, "hansken_extraction_plugin.api.transformation.RangedTransformation.builder"]], "hansken_extraction_plugin.api.transformation": [[34, "module-hansken_extraction_plugin.api.transformation"]], "length (range attribute)": [[34, "hansken_extraction_plugin.api.transformation.Range.length"]], "offset (range attribute)": [[34, "hansken_extraction_plugin.api.transformation.Range.offset"]]}}) \ No newline at end of file diff --git a/latest b/latest index a0a1517..bcaffe1 120000 --- a/latest +++ b/latest @@ -1 +1 @@ -0.6.3 \ No newline at end of file +0.7.0 \ No newline at end of file