# -*- coding:utf-8   -*-
"""The JSONPatch module provides for the alteration of JSON data compliant to RFC6902.
"""
from __future__ import absolute_import
from __future__ import print_function
from __future__ import division
import sys
import copy
# pylint: disable-msg=F0401
if sys.modules.get('json'):
    import json as myjson  # @UnusedImport
elif sys.modules.get('ujson'):
    import ujson as myjson  # @UnusedImport @Reimport @UnresolvedImport
else:
    import json as myjson  # @Reimport
# pylint: enable-msg=F0401
# for now the only one supported
from jsondata.jsonpointer import JSONPointer
from jsondata.jsondataserializer import JSONDataSerializer, MS_OFF
from jsondata.jsondata import JSONData
from jsondata import V3K, JSONDataPatchError, JSONDataPatchItemError, \
    C_SHALLOW, C_DEEP, C_REF, \
    SD_INPUT, SD_OUTPUT
__author__ = 'Arno-Can Uestuensoez'
__maintainer__ = 'Arno-Can Uestuensoez'
__license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints"
__copyright__ = "Copyright (C) 2015-2016 Arno-Can Uestuensoez" \
                " @Ingenieurbuero Arno-Can Uestuensoez"
__version__ = '0.2.21'
__uuid__ = '63b597d6-4ada-4880-9f99-f5e0961351fb'
if V3K:
    unicode = str
# Sets display for inetractive JSON/JSONschema design.
_interactive = False
#
# Operations in accordance to RFC6902
RFC6902_ADD = 1
RFC6902_COPY = 2
RFC6902_MOVE = 3
RFC6902_REMOVE = 4
RFC6902_REPLACE = 5
RFC6902_TEST = 6
#
# Mapping for reverse transformation
op2str = {
    RFC6902_ADD: "add",
    RFC6902_COPY: "copy",
    RFC6902_MOVE: "move",
    RFC6902_REMOVE: "remove",
    RFC6902_REPLACE: "replace",
    RFC6902_TEST: "test"
}
#
# Mapping for reverse transformation
str2op = {
    "add": RFC6902_ADD,
    "copy": RFC6902_COPY,
    "move": RFC6902_MOVE,
    "remove": RFC6902_REMOVE,
    "replace": RFC6902_REPLACE,
    "test": RFC6902_TEST
}
[docs]def getOp(x):
    """Converts input into corresponding enumeration.
    """
    if type(x) in (
            int,
            float, ):
        return int(x)
    elif type(x) is (
            str,
            unicode, ) and x.isdigit():
        return int(x)
    return str2op.get(x, None) 
[docs]class JSONPatchItem(object):
    """Record entry for list of patch tasks.
    Attributes:
        **op**:
            operations: ::
               add, copy, move, remove, replace, test
        **target**:
            JSONPointer for the modification target, see RFC6902.
        **value**:
            Value, either a branch, or a leaf of the JSON data structure.
        **src**:
            JSONPointer for the modification source, see RFC6902.
    """
[docs]    def __init__(self, op, target, param=None, **kargs):
        """Create an entry for the patch list.
        Args:
            **op**:
                Operation: ::
                   add, copy, move, remove, replace, test
            **target**:
                Target node. ::
                   target := (
                       <rfc6901-string>
                       | JSONPointer
                       | <path-items-list>
                       )
            **param**:
                Specific parameter for the operation.
                +-------+--------------------+
                | type  | operation          |
                +=======+====================+
                | value | add, replace, test |
                +-------+--------------------+
                | src   | copy, move         |
                +-------+--------------------+
                | param | None for 'remove'  |
                +-------+--------------------+
            kargs:
                **replace**:
                    Replace masked characters in *target* specification. ::
                       replace := (
                            True    # replaces rfc6901 escape sequences: ~0 and ~1
                          | False   # omit unescaping
                       )
                    .. note::
                    
                       Match operations are proceeded literally, thus the
                       escaped characters should be consistent,
                       see rfc6901, Section 3.
                    default := False
        Returns:
            When successful returns 'True', else returns either 'False', or
            raises an exception.
            Success is the complete addition only, thus one failure returns
            False.
        Raises:
            JSONDataPatchItemError
        """
        self.replace = kargs.get('replace', False) 
        self.value = None
        self.src = None
        self.op = getOp(op)
        self.target = JSONPointer(target, replace=self.replace)
        if self.op in (RFC6902_ADD, RFC6902_REPLACE, RFC6902_TEST):
            self.value = param
        elif self.op is RFC6902_REMOVE:
            pass
        elif self.op in (RFC6902_COPY, RFC6902_MOVE):
            self.src = param
        else:
            raise JSONDataPatchItemError("Unknown operation.") 
    def __add__(self, x=None):
        if x == None:
            raise JSONDataPatchError("Missing patch entry/patch")
        if isinstance(x, JSONPatchItem):
            ret = JSONPatch()
            ret.patch.append(self)
            ret.patch.append(x)
            return ret
        elif isinstance(x, JSONPatch):
            ret = JSONPatch(x.patch)
            ret.patch.insert(0, self)
            return ret
        else:
            raise JSONDataPatchError("Unknown input" + type(x))
[docs]    def __call__(self, jdata):
        """Evaluates the related task for the provided data.
        Args:
            **jdata**:
                JSON data the task has to be 
                applied on.
        Returns:
            Returns a tuple of: ::
               (n,lerr): 
               n:    number of present active entries
               lerr: list of failed entries
        Raises:
            JSONDataPatchError:
        """
        return self.apply(jdata) 
[docs]    def __eq__(self, x=None):
        """Compares this pointer with x.
        Args:
            **x**:
                A valid Pointer.
        Returns:
            *True* or *False*.
        Raises:
            JSONPointerError
        """
        if x == None:
            return False
        ret = True
        if type(x) == dict:
            ret &= self.target == x['path']
        else:
            ret &= self.target == x['target']
        if self.op == RFC6902_ADD:
            ret &= x['op'] in ('add', RFC6902_ADD)
            ret &= self.value == x['value']
        elif self.op == RFC6902_REMOVE:
            ret &= x['op'] in ('remove', RFC6902_REMOVE)
        elif self.op == RFC6902_REPLACE:
            ret &= x['op'] in ('replace', RFC6902_REPLACE)
            ret &= self.value == x['value']
        elif self.op == RFC6902_MOVE:
            ret &= x['op'] in ('move', RFC6902_MOVE)
            ret &= self.src == x['from']
        elif self.op == RFC6902_COPY:
            ret &= x['op'] in ('copy', RFC6902_COPY)
            ret &= self.src == x['from']
        elif self.op == RFC6902_TEST:
            ret &= x['op'] in ('test', RFC6902_TEST)
            ret &= self.value == x['value']
        return ret 
[docs]    def __getitem__(self, key):
        """Support of various mappings.
            #. self[key]
            #. self[i:j:k]
            #. x in self
            #. for x in self
        """
        if key in (
                'path',
                'target', ):
            return self.target
        elif key in ('op', ):
            return self.op
        elif key in (
                'value',
                'param', ):
            return self.value
        elif key in (
                'from',
                'src', ):
            return self.src 
[docs]    def __ne__(self, x):
        """Compares this pointer with x.
        Args:
            **x**: A valid Pointer.
        Returns:
            *True* or *False*.
        Raises:
            JSONPointerError
        """
        return not self.__eq__(x) 
    def __radd__(self, x=None):
        if x == None:
            raise JSONDataPatchError("Missing patch entry/patch")
        if isinstance(x, JSONPatchItem):
            ret = JSONPatch()
            ret.patch.append(x)
            ret.patch.append(self)
            return ret
        elif isinstance(x, JSONPatch):
            ret = JSONPatch()
            ret.patch.extend(x.patch)
            ret.patch.append(self)
            return ret
        else:
            raise JSONDataPatchError("Unknown input" + type(x))
[docs]    def __repr__(self):
        """Prints the patch string in accordance to RFC6901.
        """
        ret = '{"op": "' + unicode(op2str[self.op]) + \
            
'", "path": "' + unicode(self.target) + '"'
        if self.op in (RFC6902_ADD, RFC6902_REPLACE, RFC6902_TEST):
            if type(self.value) in (int, float):
                ret += ', "value": ' + unicode(self.value)
            elif type(self.value) in (dict, list):
                ret += ', "value": ' + repr(self.value)
            else:
                ret += ', "value": "' + unicode(self.value) + '"'
        elif self.op is RFC6902_REMOVE:
            pass
        elif self.op in (RFC6902_COPY, RFC6902_MOVE):
            ret += ', "from": "' + unicode(self.src) + '"'
        ret += "}"
        return ret 
[docs]    def __str__(self):
        """Prints the patch string in accordance to RFC6901.
        """
        ret = '{"op": "' + op2str[self.op] + \
            
'", "target": "' + str(self.target)
        if self.op in (RFC6902_ADD, RFC6902_REPLACE, RFC6902_TEST):
            if type(self.value) in (int, float):
                ret += '", "value": ' + str(self.value) + ' }'
            else:
                ret += '", "value": "' + str(self.value) + '" }'
        elif self.op is RFC6902_REMOVE:
            ret += '" }'
        elif self.op in (RFC6902_COPY, RFC6902_MOVE):
            ret += '", "src": "' + str(self.src) + '" }'
        return ret 
[docs]    def apply(self, jsondata, **kargs):
        """Applies the present patch list on the provided JSON document.
        Args:
            **jsondata**:
                Document to be patched.
            kargs:
                **replace**:
                    Replace masked characters in *target* specification. ::
                       replace := (
                            True    # replaces rfc6901 escape sequences: ~0 and ~1
                          | False   # omit unescaping
                       )
                    .. note::
                    
                       Match operations are proceeded literally, thus the
                       escaped characters should be consistent,
                       see rfc6901, Section 3.
                       If already decoded e.g. by the constructor, than should be
                       *FALSE*, is not idempotent.
                    default := False
            
        Returns:
            When successful returns 'True', else raises an exception.
            Or returns a tuple: ::
               (n,lerr): 
               n:    number of present active entries
               lerr: list of failed entries
        Raises:
            JSONDataPatchError:
        """
        replace = kargs.get('replace', self.replace) 
        if self.op is RFC6902_ADD:
            return jsondata.branch_add(
                self.value, self.target)
        if isinstance(jsondata, JSONDataSerializer):
            jsondata = jsondata.data
        if self.op is RFC6902_REPLACE:
            n, b = self.target.get_node_and_key(jsondata)
            n[b] = self.value
        elif self.op is RFC6902_TEST:
            n, b = JSONPointer(self.target, replace=replace).get_node_and_key(jsondata)
#             if type(self.value) is str:
#                 self.value = unicode(self.value)
#             if type(n) is list:
#                 return n[b] == self.value
#             return n[unicode(b)] == self.value
            if isinstance(n, list):
                return n[int(b)] == self.value
            elif isinstance(n, dict):
                return n[unicode(b)] == self.value
        elif self.op is RFC6902_COPY:
            val = JSONPointer(self.src, replace=replace).get_node_value(jsondata)
            tn, tc = self.target.get_node_and_key(jsondata)
            tn[tc] = val
        elif self.op is RFC6902_MOVE:
            val = JSONPointer(self.src, replace=replace).get_node_value(jsondata)
            sn, sc = JSONPointer(self.src, replace=replace).get_node_and_key(jsondata)
            sn.pop(sc)
            tn, tc = self.target.get_node_and_key(jsondata)
            if type(tn) is list:
                if len(tn) <= tc:
                    tn.append(val)
                else:
                    tn[tc] = val
            else:
                tn[tc] = val
        elif self.op is RFC6902_REMOVE:
            n, b = self.target.get_node_and_key(jsondata)
            n.pop(b)
        return True 
[docs]    def repr_export(self):
        """Prints the patch string for export in accordance to RFC6901.
        """
        ret = '{"op": "' + str(op2str[self.op]) + \
            
'", "path": "' + str(self.target) + '"'
        if self.op in (RFC6902_ADD, RFC6902_REPLACE, RFC6902_TEST):
            if type(self.value) in (int, float):
                ret += ', "value": ' + str(self.value)
            elif type(self.value) in (dict, list):
                ret += ', "value": ' + str(self.value)
            elif type(self.value) is None:
                ret += ', "value": null'
            elif type(self.value) is False:
                ret += ', "value": false'
            elif type(self.value) is True:
                ret += ', "value": true'
            else:
                ret += ', "value": "' + str(self.value) + '"'
        elif self.op is RFC6902_REMOVE:
            pass
        elif self.op in (RFC6902_COPY, RFC6902_MOVE):
            ret += ', "from": "' + str(self.src) + '"'
        ret += '}'
        return ret 
[docs]    def str_export(self):
        """Pretty prints the patch string for export in accordance to RFC6901.
        """
        ret = '{"op": "' + str(op2str[self.op]) + \
            
'", "path": "' + str(self.target) + '"'
        if self.op in (RFC6902_ADD, RFC6902_REPLACE, RFC6902_TEST):
            if type(self.value) in (int, float):
                ret += ', "value": ' + str(self.value)
            elif type(self.value) in (dict, list):
                ret += ', "value": ' + str(self.value)
            elif type(self.value) is None:
                ret += ', "value": null'
            elif type(self.value) is False:
                ret += ', "value": false'
            elif type(self.value) is True:
                ret += ', "value": true'
            else:
                ret += ', "value": "' + str(self.value) + '"'
        elif self.op is RFC6902_REMOVE:
            pass
        elif self.op in (RFC6902_COPY, RFC6902_MOVE):
            ret += ', "from": "' + str(self.src) + '"'
        ret += '}'
        return ret  
[docs]class JSONPatchItemRaw(JSONPatchItem):
    """Adds native patch strings or an unsorted dict for RFC6902.
    
    Calls parent *JSONPatchItem*.
    """
[docs]    def __init__(self, patchstring, **kargs):
        """Parse a raw patch string in accordance to RFC6902.
        """
        self.replace = kargs.get('replace', False) 
        if type(patchstring) in (
                str,
                unicode, ):
            ps = myjson.loads(patchstring)
            sx = myjson.dumps(ps)
            if len(sx.replace(" ", "")) != len(patchstring.replace(" ", "")):
                raise JSONDataPatchItemError(
                    "Repetition is not compliant to RFC6902:" +
                    str(patchstring))
        elif type(patchstring) is dict:
            ps = patchstring
        else:
            raise JSONDataPatchItemError("Type not supported:" +
                                         str(patchstring))
        try:
            target = ps['path']
            op = getOp(ps['op'])
            if op in (RFC6902_ADD, RFC6902_REPLACE, RFC6902_TEST):
                param = ps['value']
            elif op is RFC6902_REMOVE:
                param = None
            elif op in (RFC6902_COPY, RFC6902_MOVE):
                param = ps['from']
        except Exception as e:
            raise JSONDataPatchItemError(e)
        super(JSONPatchItemRaw, self).__init__(op, target, param, **kargs)  
[docs]class JSONPatchFilter(object):
    """Filtering capabilities on the entries of patch lists.
    
    .. warning::
    
       Not yet implemented.
    """
[docs]    def __init__(self, **kargs):
        """
        Args:
            kargs:
                Filter parameters:
                **common**:
                    contain=(True|False): Contain, else equal.
                    type=<node-type>: Node is of type.
                **paths**:
                    branch=<branch>: 
                    deep=(): Determines the depth of comparison.
                    prefix=<prefix>: Any node of prefix. If prefix is
                        absolute: the only and one, else None.
                        relative: any node prefixed by the path fragment.
                **values**:
                    val=<node-value>: Node ha the value.
        Returns:
            True or False
        Raises:
            JSONPointerError:
        """
        for k, v in kargs:
            if k == 'prefix':
                self.prefix = v
            elif k == 'branch':
                self.branch = v 
[docs]    def __eq__(self, x):
        pass 
[docs]    def __ne__(self, x):
        pass  
[docs]class JSONPatch(object):
    """ Representation of a JSONPatch task list for RFC6902.
    Contains the defined methods from standards:
    * add
    * remove
    * replace
    * move
    * copy
    * test
    Attributes:
        **patch**:
            List of patch items.
    """
[docs]    def __init__(self, p=None, **kargs):
        """List of patch tasks.
        Args:
            **p**:
                Patch list. ::
                   p := (
                        JSONPatch
                      | <list-of-patch-items>
                   )
            kargs:
                **replace**:
                    Replace masked characters in *target* specification. ::
                       replace := (
                            True    # replaces rfc6901 escape sequences: ~0 and ~1
                          | False   # omit unescaping
                       )
                    .. note::
                    
                       Match operations are proceeded literally, thus the
                       escaped characters should be consistent,
                       see rfc6901, Section 3.
                    default := False
        Returns:
            self.
        Raises:
            pass-through
        """
        assert isinstance(p, list) or p == None
        self.replace = kargs.get('replace', False) 
        if p:
            self.patch = p
        else:
            self.patch = []
        self.deep = False
        """Defines copy operations, True:=deep, False:=swallow""" 
[docs]    def __add__(self, x=None):
        """Creates and adds patch job to the task queue.
        
        Args:
            **x**:
                Extension of pathc job, eithe a *JSONPatch*,
                or a *JSONPatchItem*.
        Returns:
            Returns a patch job, or raises Exception. 
        Raises:
            JSONDataPatchError
        """
        if x == None:
            raise JSONDataPatchError("Missing patch entry/patch")
        if isinstance(x, JSONPatchItem):
            ret = JSONPatch(self.patch)
            ret.patch.append(x)
            return ret
        elif isinstance(x, JSONPatch):
            ret = JSONPatch(self.patch)
            ret.patch.extend(x.patch)
            return ret
        else:
            raise JSONDataPatchError("Unknown input" + type(x)) 
[docs]    def __call__(self, jdata, x=None):
        """Evaluates the related task for the provided index.
        Args:
            **x**: 
                Task index.
            **jdata**: 
                JSON data the task has to be 
                applied on.
        Returns:
            Returns a tuple of: ::
               (n, lerr): 
               n:    number of present active entries
               lerr: list of failed entries
        Raises:
            JSONDataPatchError:
        """
        if x  is None:
            return self.apply(jdata)
        if self.patch[x](jdata):
            return 1, []
        return 1, [0] 
[docs]    def __eq__(self, x):
        """Compares this pointer with x.
        Args:
            **x**:
                A valid Pointer.
        Returns:
            *True* or *False*
        Raises:
            JSONPointerError
        """
        if x == None:
            return False
        match = len(self.patch)
        if match != len(x):
            return False
        if isinstance(x, JSONPatch):
            return self.patch == x.patch
        elif isinstance(x, list):
            ret = True
            for i in range(match):
                for k in x[i].keys():
                    if k == 'op':
                        ret &= str(x[i][k]) == op2str[self.patch[i][k]] 
                    else:
                        ret &= str(x[i][k]) == str(self.patch[i][k]) 
            return ret
        else:
            raise JSONDataPatchError("Type requires JSONPatch or list, got" + str(type(x))) 
[docs]    def __getitem__(self, key):
        """Support of slices, for 'iterator' refer to self.__iter__.
            #. self[key]
            #. self[i:j:k]
            #. x in self
            #. for x in self
        """
        return self.patch[key] 
[docs]    def __iadd__(self, x=None):
        """Adds patch jobs to the task queue in place.
        """
        if x == None:
            raise JSONDataPatchError("Missing patch entry/patch")
        if isinstance(x, JSONPatchItem):
            self.patch.append(x)
        elif isinstance(x, JSONPatch):
            self.patch.extend(x.patch)
        else:
            raise JSONDataPatchError("Unknown input" + type(x))
        return self 
[docs]    def __isub__(self, x):
        """Removes the patch job from the task queue in place. 
        Removes one of the following type(x) variants:
            *int*:
                The patch job with given index.
            *JSONPatchItem*:
                The first matching entry from 
                the task queue. 
        Args:
            **x**:
                Item to be removed. ::
    
                   x := (
                        int
                      | JSONPatchItem
                   ) 
        Returns:
            Returns resulting list without x.
        Raises:
            JSONDataPatchError:
        """
        if type(x) is int:
            self.patch.pop(x)
        else:
            self.patch.remove(x)
        return self 
[docs]    def __iter__(self):
        """Provides an iterator foreseen for large amounts of in-memory patches.
        """
        return iter(self.patch) 
[docs]    def __len__(self):
        """The number of outstanding patches.
        """
        return len(self.patch) 
[docs]    def __ne__(self, x):
        """Compares this pointer with x.
        Args:
            **x**:
                A valid Pointer.
        Returns:
            *True* or *False*
        Raises:
            JSONPointerError
        """
        return not self.__eq__(x) 
    def __radd__(self, x=None):
        """Adds a copy of xto the task queue.
        """
        if x == None:
            raise JSONDataPatchError("Missing patch entry/patch")
        if isinstance(x, JSONPatchItem):
            ret = JSONPatch(x)
            ret.patch.append(self.patch)
            return ret
        elif isinstance(x, JSONPatch):
            ret = JSONPatch(x)
            ret.patch.extend(self.patch)
            return ret
        else:
            raise JSONDataPatchError("Unknown input" + type(x))
[docs]    def __repr__(self):
        """Prints the representation format of a JSON patch list.
        """
        ret = "["
        if self.patch:
            if len(self.patch) > 1:
                for p in self.patch[:-1]:
                    ret += repr(p) + ", "
            ret += repr(self.patch[-1])
        ret += "]"
        return unicode(ret) 
[docs]    def __str__(self):
        """Prints the display format.
        """
        ret = "[\n"
        if self.patch:
            if len(self.patch) > 1:
                for p in self.patch[:-1]:
                    ret += "  " + repr(p) + ",\n"
            ret += "  " + repr(self.patch[-1]) + "\n"
        ret += "]"
        return str(ret) 
[docs]    def __sub__(self, x):
        """Removes the patch job from the task queue. 
        Removes one of the following type(x) variants:
            *int*:
                The patch job with given index.
            *JSONPatchItem*:
                The first matching entry from 
                the task queue. 
        Args:
            **x**:
                Item to be removed. ::
                   x := (
                        int
                      | JSONPatchItem
                   ) 
        Returns:
            Returns resulting list without x.
        Raises:
            JSONDataPatchError:
        """
        ret = JSONPatch()
        if self.deep:
            ret.patch = self.patch[:]
        else:
            ret.patch = self.patch
        if type(x) is int:
            ret.patch.pop(x)
        else:
            ret.patch.remove(x)
        return ret 
[docs]    def apply(self, jsondata, **kargs):
        """Applies the JSONPatch task.
        Args:
            **jsondata**:
                JSON data the joblist has to be applied on.
        Returns:
            Returns a tuple of: ::
               (n, lerr): 
               n:    number of present active entries
               lerr: list of failed entries
        Raises:
            JSONDataPatchError:
        """
        status = []
        for p in self.patch:
            if not p.apply(jsondata, **kargs):
                # should not be called frequently
                status.append(self.patch.index(p))
        return len(self.patch), status 
[docs]    def getpatchitem(self, x=None):
        """Gets the reference to a single patch item.
        
        Args:
            **x**:
                Requested item. ::
                   x := (
                        int
                      | JSONPatchItem
                   ) 
                   int:           index of patch item
                   JSONPatchItem: the reference to the patch item
            
        Returns:
            The selected patch item.
        Raises:
            None
        """
        if x == None:
            return
        if type(x) is int:
            return self.patch[x]            
        return self.patch[self.patch.index(x)] 
[docs]    def getpatchitems(self, *args, **kargs):
        """Gets a list of references of patch items.
        
        Args:
            args:
                Requested items. ::
                   *args := (
                      <item>
                      | <item-list>
                      | None
                   )
                   item-list := <item>[, <item-list>]
                   item := (
                        int
                      | JSONPatchItem
                   )
                   None := "all items of the current patch list"
                   int:           index of patch item
                   JSONPatchItem: the reference to the patch item
            
            kargs:
                **idxlist**:
                    Print with index: ::
                       idxlist := (
                            True     # format: [{<index>: <JSONPatchItem>}]
                          | False    # format: [<JSONPatchItem>]
                       )
                **copydata**:
                    Creates a copy of each resulting item. ::
                       copydata := (C_DEEP | C_SHALLOW | C_REF)
                    default := C_REF  # no copy
                
        Returns:
            A list of the selected patch items.
        Raises:
            None
        """
        ret = []
        _copy = kargs.get('copydata', C_REF)
        if args and args[0] == None:
            args = self.patch
        for x in args:
            if type(x) is int:
                _c = self.patch[x]
            else:
                x = self.patch.index(x)
                _c = self.patch[x]
            if _copy == C_SHALLOW:
                _c = copy.copy(_c)
            elif _copy == C_DEEP:
                _c = copy.deepcopy(_c)
            if kargs.get('idxlist'):
                ret.append({x: _c})
            else:
                ret.append(_c)
        return ret 
[docs]    def gettree(self, *args, **kargs):
        """Gets the resulting logical JSON data structure
        constructed from the patch items of the current set.
        
        Args:
            args:
                Requested items. ::
                   *args := (
                      <item>
                      | <item-list>
                      | None
                   )
                   item-list := <item>[, <item-list>]
                   item := (
                        int
                      | JSONPatchItem
                   )
                   None := "all items of the current patch list"
                   int:           index of patch item
                   JSONPatchItem: the reference to the patch item
            
            kargs:
                **data**:
                    An optional JSON data structure, when provided
                    the actual data as selected by the patch list
                    is returned. Else the paths only.
                    default := None
                **scope**:
                    Defines the source scope of the data structure. ::
                       scope := (
                            "in"   # input data, e.g. source for "copy"
                          | "out"  # output data, e.g. target for "copy
                       )
                    default := "out"
        Returns:
            The combined list of the selected patch items contained
            in an object JSONData.
        Raises:
            None
        """
        _data = kargs.get('data', None)
        _scope = kargs.get('scope', 'out')
        if _scope == 'in':
            _scope = True
        else:
            _scope = False
        _pl = self.getpatchitems(*args)
        ret = JSONData('')
        
        for x in _pl:
            if _scope == SD_INPUT:
                if x['op'] in (RFC6902_ADD,):
                    ret.branch_add('add', x['path'])  
                elif x['op'] in (RFC6902_COPY,):
                    ret.branch_add('copy', x['from'])  
                elif x['op'] in (RFC6902_MOVE,):
                    ret.branch_add('move', x['from'])  
                elif x['op'] in (RFC6902_REMOVE,):
                    ret.branch_add('remove', x['path'])  
                elif x['op'] in (RFC6902_REPLACE,):
                    ret.branch_add('replace', x['path'])  
                elif x['op'] in (RFC6902_TEST,):
                    ret.branch_add('test', x['path'])  
            elif _scope == SD_OUTPUT:
                if x['op'] in (RFC6902_ADD,):
                    ret.branch_add('add', x['path'])  
                elif x['op'] in (RFC6902_COPY,):
                    ret.branch_add('copy', x['path'])  
                elif x['op'] in (RFC6902_MOVE,):
                    ret.branch_add('move', x['path'])  
                elif x['op'] in (RFC6902_REMOVE,):
                    ret.branch_add('remove', x['path'])  
                elif x['op'] in (RFC6902_REPLACE,):
                    ret.branch_add('replace', x['path'])  
                elif x['op'] in (RFC6902_TEST,):
                    ret.branch_add('test', x['path'])  
            else:  # both
                if x['op'] in (RFC6902_ADD,):
                    ret.branch_add('add', x['path'])  
                elif x['op'] in (RFC6902_COPY,):
                    ret.branch_add('copy', x['from'])  
                    ret.branch_add('copy', x['path'])  
                elif x['op'] in (RFC6902_MOVE,):
                    ret.branch_add('move', x['from'])  
                    ret.branch_add('move', x['path'])  
                elif x['op'] in (RFC6902_REMOVE,):
                    ret.branch_add('remove', x['path'])  
                elif x['op'] in (RFC6902_REPLACE,):
                    ret.branch_add('replace', x['path'])  
                elif x['op'] in (RFC6902_TEST,):
                    ret.branch_add('test', x['path'])  
        return ret 
[docs]    def patch_export(self, patchfile, schema=None, **kargs):
        """Exports the current task list.
        Args:
            **patchfile**:
                JSON patch for export.
            **schema**:
                JSON-Schema for validation of the patch list.
            kargs:
                **validator**: [default, draft3, off, ]
                    Sets schema validator for the data file.
                    The values are: ::
                       default = validate
                       draft3  = Draft3Validator
                       off     = None
                    default:= validate
                **pretty**:
                    If True exports as tree format, 
                    else all as one line.
        Returns:
            When successful returns 'True', else raises an exception.
        Raises:
            JSONDataPatchError:
        """
        pretty = kargs.get('pretty', False)
        if not pretty:
            try:
                with open(patchfile, 'w') as fp:
                    fp.writelines(self.repr_export())
            except Exception as e:
                raise JSONDataPatchError("open-" + str(e), "data.dump",
                                         str(patchfile))
        else:
            try:
                with open(patchfile, 'w') as fp:
                    fp.writelines(str(self.str_export()))
            except Exception as e:
                raise JSONDataPatchError("open-" + str(e), "data.dump",
                                         str(patchfile))
        return True 
[docs]    def patch_import(self, patchfile, schemafile=None, **kargs):
        """Imports a task list.
        Args:
            **patchfile**:
                JSON patch filename containing the list of patch operations.
            **schemafile**:
                JSON-Schema filename for validation of the patch list.
            kargs:
                **replace**:
                    Replace masked characters in *target* specification. ::
                       replace := (
                            True    # replaces rfc6901 escape sequences: ~0 and ~1
                          | False   # omit unescaping
                       )
                    .. note::
                    
                       Match operations are proceeded literally, thus the
                       escaped characters should be consistent,
                       see rfc6901, Section 3.
                    default := False
                **validator**:
                    Sets schema validator for the data file.
                    Curren release relies on *jsonschema*, which
                    supports at the time of writing draft-03 and
                    draft-04.
                    The values are: ::
                        validator := (
                              MS_DRAFT3           | 'draft3'
                            | MS_DRAFT4           | 'draft4'
                            | MS_ON               | 'on'
                            | MS_OFF              | 'off'
                            | MODE_SCHEMA_DEFAULT | 'default'
                        )
                    default:= MS_OFF
        Returns:
            When successful returns 'True', else raises an exception.
        Raises:
            JSONDataPatchError:
        """
        replace = kargs.get('replace', self.replace) 
        validator = kargs.get('validator', MS_OFF)
        patchdata = JSONDataSerializer(
            [],
            datafile=patchfile,
            schemafile=schemafile,
            validator=MS_OFF,
            )
        for pi in patchdata.data:
            self += JSONPatchItemRaw(pi, replace=replace)
        return True 
[docs]    def repr_export(self):
        """Prints the export representation format of a JSON patch list.
        """
        ret = "["
        if self.patch:
            if len(self.patch) > 1:
                for p in self.patch[:-1]:
                    ret += p.repr_export() + ", "
            ret += self.patch[-1].repr_export()
        ret += "]"
        return ret 
[docs]    def str_export(self):
        """Pretty prints the export representation format of a JSON patch list.
        """
        ret = "[\n"
        if self.patch:
            if len(self.patch) > 1:
                for p in self.patch[:-1]:
                    ret += "  " + p.str_export() + ",\n"
            ret += "  " + self.patch[-1].str_export() + "\n"
        ret += "]"
        return str(ret)