+++ /dev/null
-;;; x-path-walker-autoloads.el --- automatically extracted autoloads
-;;
-;;; Code:
-(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
-\f
-;;;### (autoloads nil "x-path-walker" "x-path-walker.el" (22477 28061
-;;;;;; 0 0))
-;;; Generated autoloads from x-path-walker.el
-
-(autoload 'helm-x-path-walker "x-path-walker" "\
-
-
-\(fn)" t nil)
-
-;;;***
-\f
-;;;### (autoloads nil nil ("x-path-walker-pkg.el") (22477 28061 263065
-;;;;;; 0))
-
-;;;***
-\f
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; End:
-;;; x-path-walker-autoloads.el ends here
+++ /dev/null
-(define-package "x-path-walker" "20160822.1015" "Navigation feature for JSON/XML/HTML based on path (imenu like)"
- '((helm-core "1.9.2"))
- :keywords
- '("convenience"))
-;; Local Variables:
-;; no-byte-compile: t
-;; End:
+++ /dev/null
-;;; x-path-walker.el --- Navigation feature for JSON/XML/HTML based on path (imenu like) -*- lexical-binding: t; -*-
-
-;; Copyright (C) 2015
-
-;; Author: <lompik@ArchOrion>
-;; Keywords: convenience
-;; Package-Requires: ((helm-core "1.9.2"))
-
-;; This program is free software; you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
-
-;; This program 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 for more details.
-
-;; You should have received a copy of the GNU General Public License
-;; along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;;
-
-;;; Code:
-
-(require 'helm)
-
-(defvar x-path-walker-source-dir (concat (if load-file-name
- (file-name-directory load-file-name)
- default-directory)))
-
-(defvar x-path-walker-objects-separators ".")
-
-(defun x-path-walker-command ()
- (list (concat "PYTHONPATH=" x-path-walker-source-dir)
- (executable-find "python3")
- "-m xpathwalker"))
-
-(defvar x-path-walker-verbose nil)
-
-(defun x-path-build-cmd-path (args)
- (let ((cmdpython (x-path-walker-command)))
- (mapconcat #'identity
- (append cmdpython
- args)
- " ")))
-
-(defun x-path-run-py-script (args)
- (let ((cmd (x-path-build-cmd-path args)))
- (shell-command-to-string cmd)))
-
-(defun x-path-get-mode ()
- (let ((mm (pcase major-mode
- (`json-mode "JSON")
- (`xml-mode "XML")
- (`nxml-mode "XML")
- (`html-mode "HTML")
- (`web-mode "XML")
- (code nil))))
- (unless (and (buffer-file-name) mm)
- (keyboard-quit))
- mm))
-
-(defun x-path-walker-ask ()
- (interactive)
- (if (and (buffer-modified-p)
- (yes-or-no-p "Buffer will be linted. Do you want to save the current buffer (required)?"))
- (save-current-buffer)
- (if (buffer-modified-p)
- (keyboard-quit))))
-
-(defun x-path-walker-jump-path (path)
- (let* ((mode (x-path-get-mode))
- (path (if (not (bound-and-true-p x-path-walker-verbose))
- path
- (if (or (string= mode "HTML")
- (string= mode "XML"))
- (car (split-string path " | "))
- (if (and (string= mode "JSON") )
- (car (cdr (split-string path " | ")))
- ""))))
- (line (replace-regexp-in-string
- "\n$" ""
- (x-path-run-py-script `("-x"
- ,(shell-quote-argument path)
- "-m"
- ,mode
- ,(buffer-file-name) )))))
- (if (string= mode "JSON")
- (progn (x-path-walker-ask)
- (erase-buffer)
- (shell-command-on-region (point-min) (point-max)
- (concat "python3 -m json.tool "
- (buffer-file-name))
- (current-buffer))))
- (unless (or (string= "" line))
- (goto-char 0)
- (forward-line (1- (string-to-number line)))
- (recenter)
- (back-to-indentation))))
-
-;;;###autoload
-(defun helm-x-path-walker()
- (interactive)
- (let* ((mode (x-path-get-mode))
- (file (buffer-file-name))
- (cmd-line `(,(if (bound-and-true-p x-path-walker-verbose)
- "-a"
- "")
- "-m"
- ,mode
- ,file ))
- (cands (split-string (x-path-run-py-script cmd-line)"\n")))
- (helm
- :sources (helm-build-sync-source "PATH-WALKER"
- :keymap helm-map
- :candidates cands
- :candidate-number-limit 500
- :action (helm-make-actions
- "Jump to path" 'x-path-walker-jump-path))
- :prompt "Select Path:"
- :resume 'noresume
- :keymap helm-map
- :buffer "*helm path-walker*")))
-
-(provide 'x-path-walker)
-;;; x-path-walker.el ends here
+++ /dev/null
-# xpathwalker
-
-has_legs = False
+++ /dev/null
-import sys
-
-from xpathwalker.args import parse_args
-
-
-##########################################################################
-# Main #
-##########################################################################
-
-def main(argv):
- parse_args(argv)
-
-
-if __name__ == "__main__":
- main(sys.argv[1:])
-
-# Bench:
-# wc -l
-# 21302 /mnt/VM/home/lompik/Programmation/Web/d3_Map_GOM/Data/fldunit.json
-# with multiproc:
-# # string | .features[99].properties.UNIT_AGT_N
-# # string | .features[99].properties.VERSION_DA
-# # string | .features[99].type
-# # string | .features[9].geometry.type
-# # string | .features[9].properties.OUTLINE_TY
-# # string | .features[9].properties.SDE_TEXT_L
-# # string | .features[9].properties.UNIT_AGT_1
-# # string | .features[9].properties.UNIT_AGT_N
-# # string | .features[9].properties.VERSION_DA
-# # string | .features[9].type
-# # string | .type
-# # ./xmlxpath.py -a -M 374.99s user 29.73s system 569% cpu 1:11.05 total
-
-# without multiproc:
-# # array | .features[9].geometry.coordinates[1][0][7]
-# # number | .features[9].geometry.coordinates[1][0][7][0]
-# # number | .features[9].geometry.coordinates[1][0][7][1]
-# # array | .features[9].geometry.coordinates[1][0][8]
-# # number | .features[9].geometry.coordinates[1][0][8][0]
-# # number | .features[9].geometry.coordinates[1][0][8][1]
-# # array | .features[9].geometry.coordinates[1][0][9]
-# # number | .features[9].geometry.coordinates[1][0][9][0]
-# # number | .features[9].geometry.coordinates[1][0][9][1]
-# # string | .features[9].geometry.type
-# # object | .features[9].properties
-# # string | .features[9].properties.OUTLINE_TY
-# # string | .features[9].properties.SDE_TEXT_L
-# # number | .features[9].properties.SEQUENCE_N
-# # number | .features[9].properties.SHAPE_Area
-# # number | .features[9].properties.SHAPE_Leng
-# # number | .features[9].properties.SN_UNIT_AL
-# # number | .features[9].properties.SN_UNIT_OU
-# # string | .features[9].properties.UNIT_AGT_1
-# # string | .features[9].properties.UNIT_AGT_N
-# # string | .features[9].properties.VERSION_DA
-# # string | .features[9].type
-# # string | .type
-# # ./xmlxpath.py -a 241.04s user 13.05s system 93% cpu 4:31.48 total
+++ /dev/null
-from argparse import ArgumentParser
-import os
-import sys
-
-exts_mode = {'.html': "HTML", '.html': 'HTML', '.xml': 'XML', '.json': 'JSON', '.yaml': 'YAML'}
-
-def parse_args(argv):
- """parse command line arguments
-
- :param argv:
- :returns: Processor class
- :rtype:
-
- """
- default_separator = "."
-
- parser = ArgumentParser(description="Xpath helper")
- parser.add_argument("inputfile",
- help="Input FILE", type=str)
- parser.add_argument("-x", "--xpath", dest='xpath',
- help="Returns line number corresponding to xpath from FILE", type=str)
- parser.add_argument("-m", "--mode", dest='mode',
- help="Language type", type=str, choices=('XML', 'HTML', 'JSON'))
- parser.add_argument("-a", dest='verbosity', action="store_true", default=False,
- help="Increase verbosity")
- parser.add_argument("-M", dest='multiproc', action="store_true", default=False,
- help="Use multiple process when possible")
- parser.add_argument("-j", dest='use_jq', action="store_true", default=False,
- help="JSON: Use jq for processing")
- parser.add_argument("-F", dest='separator', default=default_separator, type=str,
- help="Path elements separator")
- args = parser.parse_args(argv)
-
- inputfile = args.inputfile
- proc = False
- if(not os.path.isfile(inputfile)):
- sys.exit(1)
-
- ext = os.path.splitext(inputfile)[1].lower()
-
- if(args.mode is None):
- if ext not in exts_mode.keys():
- print("Unrecognized file format.")
- sys.exit(1)
- else:
- args.mode = exts_mode[ext]
-
- if(args.mode == 'HTML'):
- from xpathwalker.lxml_Processor import HTML_Processor
- proc = HTML_Processor()
- if args.xpath:
- args.xpath = "/"+args.xpath
- elif(args.mode == 'XML'):
- from xpathwalker.lxml_Processor import XML_Processor
- proc = XML_Processor()
- if args.xpath:
- args.xpath = "/"+args.xpath
- elif(args.mode == 'JSON'):
- if(args.use_jq):
- from xpathwalker.json_jq_Processor import JSON_jq_Processor
- proc = JSON_jq_Processor()
- else:
- from xpathwalker.json_Processor import JSON_Processor
- proc = JSON_Processor()
- else:
- print("Error in arg parsing")
- sys.exit(2)
-
- proc.separator = args.separator
- proc.parse_file(inputfile)
-
- if(args.xpath is not None):
- print(proc.get_path_line_number(args.xpath, args.verbosity))
- else:
- print("\n".join(proc.list_paths(args.verbosity)))
+++ /dev/null
-from xpathwalker.utils import list_compare_idx
-from xpathwalker.processor import Processor
-from xpathwalker.utils import traverse_and_remove_path,\
- get_dict_from_path, get_class
-import json
-from collections import MutableSequence, MutableMapping
-from collections import OrderedDict
-
-##########################################################################
-# JSON / Python #
-##########################################################################
-
-
-class JSON_Processor(Processor):
- file_line_number = 0
-
- def isValidJsonShorthandPropertyName(self, key):
- if not key[0].isalpha() or not key.isalnum():
- return('["' + key + '"]')
- else:
- return(self.separator + key)
-
-
- def paths(self, obj, dirs=True, leaves=True, path='', skip=False, allow_empty_string_keys=False):
- """Yield all paths of the object.
- Arguments:
- obj -- An object to get paths from.
- Keyword Arguments:
- dirs -- Yield intermediate paths.
- leaves -- Yield the paths with leaf objects.
- path -- A list of keys representing the path.
- skip -- Skip special keys beginning with '+'.
- """
- if isinstance(obj, MutableMapping):
- # Python 3 support
- # if PY3:
- iteritems = obj.items()
-
- for (k, v) in iteritems:
- if issubclass(k.__class__, (str)):
- if (not k) and (not allow_empty_string_keys):
- print("Empty string keys not allowed without "
- "dpath.options.ALLOW_EMPTY_STRING_KEYS=True")
- elif (skip and k[0] == '+'):
- continue
- newpath = path + self.isValidJsonShorthandPropertyName(str(k))
- # validate(newpath)
- if dirs:
- yield {newpath: {"class": v.__class__}}
- for child in self.paths(v, dirs, leaves, newpath, skip):
- yield child
-
- elif isinstance(obj, MutableSequence):
- for (i, v) in enumerate(obj):
- newpath = path + "[%s]" % i
- if issubclass(i.__class__, int):
- newpath = path + "[%d]" % i
- if dirs:
- yield {newpath: {"class": v.__class__}}
- for child in self.paths(obj[i], dirs, leaves, newpath, skip):
- yield child
- elif leaves:
- yield path + [[obj, obj.__class__]]
- elif not dirs:
- yield path
-
- def serialize_json(self):
- res = OrderedDict()
- for x in self.paths(self.parsed, leaves=False):
- res.update(x)
- return(res)
-
- def list_paths(self, with_attrs=True):
- json_paths = self.serialize_json()
- if(with_attrs):
- return(["%6s | %s" % (get_class(json_paths[x]["class"]), x) for x in json_paths.keys()])
- else:
- return([x for x in json_paths.keys()])
-
- def get_path_line_number(self, path, with_attrs=False):
- path = path.split("| ")[1] if (with_attrs is True) else path
-
- orig = self.parsed
- path = get_dict_from_path(path, self.separator)
- res = traverse_and_remove_path(orig, path)
- orig = json.dumps(orig, indent=4).split("\n")
-
- if(len(orig) != self.file_line_number):
- raise ValueError("This json file formatting is not compatible. Please run `python -m json.tool file`")
-
- with_del = json.dumps(res, indent=4).split("\n")
- return(list_compare_idx(orig, with_del) + 1)
-
- def parse_file(self, file):
- with open(file) as f:
- self.file_line_number = len(f.readlines())
- f.seek(0)
- self.parsed = json.load(f, object_pairs_hook=OrderedDict)
+++ /dev/null
-import subprocess
-import sys
-import os
-from tempfile import NamedTemporaryFile
-from xpathwalker.utils import list_compare_idx
-from xpathwalker.processor import Processor
-
-##########################################################################
-# JSON #
-##########################################################################
-
-jq_command = """def safeLookupValue:
- if type != "string" then
- tostring
- else
- .
- end;
-\
-def arrayToLookup:
- map(safeLookupValue)
- | reduce .[] as $exists (
- {};
- . + {
- ($exists): null
- }
- );
-\
-def lookup(value):
- (value | safeLookupValue) as $value
- | has($value);
-\
-def isWhitelisted(whitelist):
- whitelist as $whitelist
- | explode
- | map(
- . as $charCode
- | $whitelist
- | lookup($charCode)
- )
- | all;
-\
-def isNumeric:
- ("0123456789" | explode | arrayToLookup) as $digits
- | isWhitelisted($digits);
-\
-def isAlpha:
- ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | explode | arrayToLookup) as $letters
- | isWhitelisted($letters);
-\
-def isAlphanumeric:
- ("0123456789" | explode | arrayToLookup) as $digits
- | ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | explode | arrayToLookup) as $letters
- | ("_" | explode | arrayToLookup) as $special
- | ($digits + $letters + $special) as $alphanumeric
- | isWhitelisted($alphanumeric);
-\
-def isValidJsonShorthandPropertyName:
- (.[0:1] | isAlpha | not) or (isAlphanumeric | not);
-\
-[
- path(..)
- | map(
- if type == "number" then
- ["[",(.|tostring),"]"]|join("")
- else
- tostring
- | if isValidJsonShorthandPropertyName then
- "[\\"\\(.)\\"]"
- else
- .
- end
- end
- )
- | join(".")
- | split("." + "[")
- | join("[")
-]
-| unique
-| map("." + .)
-| .[]"""
-
-jq_exec = ["jq", "--raw-output"]
-
-def jq_proc(args):
- with NamedTemporaryFile() as f:
- try:
- proc = subprocess.check_call(args,
- stdout=f,
- stderr=subprocess.STDOUT)
- except subprocess.CalledProcessError:
- print(proc.stderr.fseek(0).read().decode('utf-8'))
- sys.exit(1)
- f.seek(0)
- return f.read().decode('utf-8').split('\n')
-
-
-class JSON_jq_Processor(Processor):
- file = ""
-
- def list_paths(self, with_attrs=True, multiproc=False):
- if(not os.path.isfile(self.file)):
- sys.exit(1)
- proc = jq_proc(args=jq_exec + [jq_command,
- self.file])
- paths = [x for x in proc if x != '']
- res = []
- if(with_attrs is False):
- res = paths
- elif with_attrs is True:
- if multiproc is False:
- for x in paths:
- proc = jq_proc(args=jq_exec + ["%s|type" % x,
- self.file])
- type = proc[0]
- res.append("%6s | %s" % (type[0:6], x))
- else:
- def get_types(q, x, file):
- proc = jq_proc(args=jq_exec + ["%s|type" % x,
- self.file])
- type = proc[0]
- q.put("%6s | %s" % (type[0:6], x))
- import queue
- import threading
- q = queue.PriorityQueue()
- for x in paths:
- t = threading.Thread(target=get_types, args=(q, x, self.file))
- t. start()
- while(q.qsize() > 0):
- res.append(q.get())
- return(res)
-
- def get_path_line_number(self, path, with_attrs=False):
- path = path.split("| ")[1] if (with_attrs is True) else path
- proc = jq_proc(args=jq_exec + ['.',
- self.file])
- orig = proc
- proc = jq_proc(args=jq_exec + ['del(%s)' % path,
- self.file])
- with_del = proc
- return(list_compare_idx(orig, with_del) + 1)
-
- def parse_file(self, file):
- if(not os.path.isfile(file)):
- print("File %s: Not Found" % (file))
- sys.exit(1)
- self.file = file
+++ /dev/null
-from xpathwalker.processor import Processor
-import os
-import sys
-import lxml.etree
-import lxml.html
-
-
-class lxmlProcessor(Processor):
- """ [X/HT]ML processor
- """
- def list_paths(self, with_attrs=False):
- if(not self.parsed):
- raise ValueError("No data in parsed")
- parsed = self.parsed
- res = []
- for el in parsed.xpath("//*"):
- if(with_attrs):
- if el.keys() is None:
- res.append("/%s | %s | %s" % (parsed.getelementpath(el), ",".join(el.keys())))
- else:
- res.append("/%s | %s" % (parsed.getelementpath(el),
- ",".join(["%s: %s" % (attr, el.get(key=attr)) for attr in el.keys()])))
- else:
- res.append("/%s" % (parsed.getelementpath(el)))
- return(res)
-
- def handle_namespace_in_xpath(self, xpath):
- closinp = len(xpath.split("}"))
- if(closinp > 1 and closinp != len(xpath.split("}"))):
- return xpath
- import re
- import string
- insidep = r"(?<={)[^}]*"
- namesspaces = re.findall(insidep, xpath)
- nsdict = {}
-
- def get_letters(matchobj):
- get_letters.counter += 1
- id = string.ascii_letters[get_letters.counter]
- nsdict.update({id: namesspaces[get_letters.counter]})
- return id + ":"
- get_letters.counter = -1
- par = re.compile(r"{"+insidep+r"}")
- xpath = par.sub(get_letters, xpath)
- return (xpath, {"namespaces": nsdict})
-
- def get_path_line_number(self, xpath, with_attrs=False):
- if(not self.parsed):
- raise ValueError("No data in parsed")
-
- parsed = self.parsed
- xpath = xpath.split(" | ")[0] if (with_attrs is True or xpath.find(" | ") > -1) else xpath
- res = 0
- matchs = []
-
- if(xpath.find("{") > -1): # Handle namespace cases
- xpath, args = self.handle_namespace_in_xpath(xpath)
- matchs = parsed.xpath(xpath, **args)
- else:
- matchs = parsed.xpath(xpath)
- if(len(matchs) > 0):
- res = matchs[0].sourceline
- return(res)
-
-class HTML_Processor(lxmlProcessor):
- def parse_file(self, file):
- """HTML file parse from lxml
-
- :param file: file path (string)
- :returns: null
- :rtype:
-
- """
- self.parsed = lxml.html.parse(file)
-
-class XML_Processor(lxmlProcessor):
- def parse_file(self, file):
- """HTML file parse from lxml
-
- :param file: file path (string)
- :returns: null
- :rtype:
-
- """
- self.parsed = lxml.etree.parse(file)
+++ /dev/null
-
-
-class Processor:
- """An abstract object class to handle the parsing/procesing of files
-
- """
- separator = "."
-
- def __init__(self):
- super(Processor, self).__init__()
-
- def list_paths(self, parsed, with_attrs=False):
- """Return array of path separated by the separator
-
- :param parsed: A python type object
- :param with_attrs: List additional attributes
- :returns: array of paths
- :rtype: array
-
- """
- pass
-
- def get_path_line_number(self, path, with_attrs=False):
- """Returns the line number from the file corresponding `path`
-
- :param path: Path seeked
- :param with_attrs: `True` if `path` is formatted with additional attributes
- :returns: line numer corresponding to path
- :rtype: int
-
- """
- pass
-
- def parse_file(self, file):
- """Translate the `file` into python object
-
- :param file: File path
- :returns: Nothing. insternal variable updated.
- :rtype:
-
- """
- pass
+++ /dev/null
-from collections import OrderedDict
-
-##########################################################################
-# Utils #
-##########################################################################
-
-def get_class(obj):
- name = obj.__name__
- if(name == "OrderedDict"):
- return("object")
- elif(name == "NoneType"):
- return("null")
- return(name)
-
-def list_compare_idx(list1, list2):
- index = 0
- for idx, origline in enumerate(list1):
- if(list2[idx] != origline):
- if((list2[idx]+",") != origline):
- index = idx
- break
- return(index)
-
-def split_path(path, separator, inside_special=False):
- first_match = '"]' if inside_special else '["'
- i = path.find(first_match)
- if i != -1 and inside_special is False:
- if(path[0:i] != ''):
- return (split_path(path[0:i],
- separator=separator,
- inside_special=False) +
- split_path(path[i+len('["'):],
- separator=separator,
- inside_special=True))
- else:
- return (split_path(path[i+len('["'):],
- separator=separator,
- inside_special=True))
- if i != -1 and inside_special is True:
- if(path[i+len('["'):] != ''):
- return ([path[:i], "special"],) + split_path(path[i+len('["'):],
- separator=separator,
- inside_special=False)
- else:
- return ([path[:i], "special"],)
- else:
- i = path.find(separator)
- if i == -1:
- return (path,)
- else:
- if(i != 0):
- return (path[:i],) + split_path(path[i+1:], separator)
- else:
- return split_path(path[i+1:], separator)
-
-
-def get_dict_from_path(path, separator):
- path = split_path(path, separator)
-
- res = []
- for x in path:
- if not isinstance(x, list):
- pos = x.find("[")
- while pos != -1:
- if pos == 0:
- i1 = x.find("]")
- x1 = int(x[0:i1].replace("[", "", 1).replace("]", "", 1))
- x = x[i1+1:]
- res.append(x1)
- else:
- res.append(x[0:pos])
- x = x[pos:]
- pos = x.find("[")
- else:
- res.append(x[0])
- return(res)
-
-def traverse_and_remove_path(obj, path=None, match="First"):
- if path is None:
- path = []
- if isinstance(obj, dict):
- res = OrderedDict()
- for k, v in obj.items():
- cmatch = False
- if(match and len(path) > 0 and path[0] == k):
- cmatch = True
- res.update({k: traverse_and_remove_path(v, path=path[1:], match=cmatch)})
- if(len(path) == 1 and path[0] in res.keys() and match):
- del res[path[0]]
- return res
- elif isinstance(obj, list):
- res = []
- for i, elem in enumerate(obj):
- cmatch = False
- if(match and len(path) >= 1 and isinstance(path[0], int) and path[0] < len(obj) and i == path[0]):
- cmatch = True
- res.append(traverse_and_remove_path(elem, path=path[1:], match=cmatch))
- if(len(path) == 1 and isinstance(path[0], int) and path[0] < len(res) and match):
- res.pop(path[0])
- return res
- else:
- return obj # no container, just values (str, int, float)
+++ /dev/null
-##########################################################################
-# YAML #
-##########################################################################
-import yaml
-from xpathwalker.utils import list_compare_idx
-from xpathwalker.utils import traverse_and_remove_path, get_dict_from_path, get_class, paths
-from collections import OrderedDict
-import os
-
-def parse_yaml(file):
- with open(file) as f:
- yaml_data = yaml.load(f)
- res = OrderedDict()
- for x in paths(yaml_data, leaves=False):
- res.update(x)
- return(res)
-
-def get_yaml_paths(file, include_type=True): # XXX: ordering is lost
- yaml_paths = parse_yaml(file)
- if(include_type is True):
- return(["%s | %s" % (get_class(yaml_paths[x]['class']), x) for x in yaml_paths.keys()])
- else:
- return(yaml_paths.keys())
-
-def get_yaml_path_line_python(file, path, include_type=False):
- if(not os.path.isfile(file)):
- sys.exit(1)
- path = path.split("| ")[1] if (include_type is True) else path
- orig = ""
- with open(file) as f:
- orig = yaml.load(f)
- path = get_dict_from_path(path)
- res = traverse_and_remove_path(orig, path)
- yaml_dump_options = {"indent": 4, "default_flow_style": False, "explicit_start": False}
- orig = yaml.dump(orig, **yaml_dump_options).split("\n")
- with_del = yaml.dump(dict(res), **yaml_dump_options).split("\n")
- return(list_compare_idx(orig, with_del) + 1)
--- /dev/null
+;;; x-path-walker-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+
+(add-to-list 'load-path (directory-file-name
+ (or (file-name-directory #$) (car load-path))))
+
+\f
+;;;### (autoloads nil "x-path-walker" "x-path-walker.el" (0 0 0 0))
+;;; Generated autoloads from x-path-walker.el
+
+(autoload 'helm-x-path-walker "x-path-walker" "\
+
+
+\(fn)" t nil)
+
+(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "x-path-walker" '("x-path-")))
+
+;;;***
+\f
+;;;### (autoloads nil nil ("x-path-walker-pkg.el") (0 0 0 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; x-path-walker-autoloads.el ends here
--- /dev/null
+(define-package "x-path-walker" "20160922.1835" "Navigation feature for JSON/XML/HTML based on path (imenu like)"
+ '((helm-core "1.9.2"))
+ :keywords
+ '("convenience")
+ :authors
+ '((nil . "<lompik@ArchOrion>"))
+ :maintainer
+ '(nil . "<lompik@ArchOrion>"))
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; x-path-walker.el --- Navigation feature for JSON/XML/HTML based on path (imenu like) -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2015
+
+;; Author: <lompik@ArchOrion>
+;; Keywords: convenience
+;; Package-Requires: ((helm-core "1.9.2"))
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program 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 for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(require 'helm)
+
+(defvar x-path-walker-source-dir (concat (if load-file-name
+ (file-name-directory load-file-name)
+ default-directory)))
+
+(defvar x-path-walker-objects-separators ".")
+
+(defun x-path-walker-command ()
+ (list (concat "PYTHONPATH=" x-path-walker-source-dir)
+ (executable-find "python3")
+ "-m xpathwalker"))
+
+(defvar x-path-walker-verbose nil)
+
+(defun x-path-build-cmd-path (args)
+ (let ((cmdpython (x-path-walker-command)))
+ (mapconcat #'identity
+ (append cmdpython
+ args)
+ " ")))
+
+(defun x-path-run-py-script (args)
+ (let ((cmd (x-path-build-cmd-path args)))
+ (shell-command-to-string cmd)))
+
+(defun x-path-get-mode ()
+ (let ((mm (pcase major-mode
+ (`json-mode "JSON")
+ (`xml-mode "XML")
+ (`nxml-mode "XML")
+ (`html-mode "HTML")
+ (`web-mode "HTML")
+ (code nil))))
+ (unless (and (buffer-file-name) mm)
+ (keyboard-quit))
+ mm))
+
+(defun x-path-walker-ask ()
+ (interactive)
+ (if (and (buffer-modified-p)
+ (yes-or-no-p "Buffer will be linted. Do you want to save the current buffer (required)?"))
+ (save-current-buffer)
+ (if (buffer-modified-p)
+ (keyboard-quit))))
+
+(defun x-path-walker-jump-path (path)
+ (let* ((mode (x-path-get-mode))
+ (path (if (not (bound-and-true-p x-path-walker-verbose))
+ path
+ (if (or (string= mode "HTML")
+ (string= mode "XML"))
+ (car (split-string path " | "))
+ (if (and (string= mode "JSON") )
+ (car (cdr (split-string path " | ")))
+ ""))))
+ (line (replace-regexp-in-string
+ "\n$" ""
+ (x-path-run-py-script `("-x"
+ ,(shell-quote-argument path)
+ "-m"
+ ,mode
+ ,(buffer-file-name) )))))
+ (if (string= mode "JSON")
+ (progn (x-path-walker-ask)
+ (erase-buffer)
+ (shell-command-on-region (point-min) (point-max)
+ (concat "python3 -m json.tool "
+ (buffer-file-name))
+ (current-buffer))))
+ (unless (or (string= "" line))
+ (goto-char 0)
+ (forward-line (1- (string-to-number line)))
+ (recenter)
+ (back-to-indentation))))
+
+;;;###autoload
+(defun helm-x-path-walker()
+ (interactive)
+ (let* ((mode (x-path-get-mode))
+ (file (buffer-file-name))
+ (cmd-line `(,(if (bound-and-true-p x-path-walker-verbose)
+ "-a"
+ "")
+ "-m"
+ ,mode
+ ,file ))
+ (cands (split-string (x-path-run-py-script cmd-line)"\n")))
+ (helm
+ :sources (helm-build-sync-source "PATH-WALKER"
+ :keymap helm-map
+ :candidates cands
+ :candidate-number-limit 500
+ :action (helm-make-actions
+ "Jump to path" 'x-path-walker-jump-path))
+ :prompt "Select Path:"
+ :resume 'noresume
+ :keymap helm-map
+ :buffer "*helm path-walker*")))
+
+(provide 'x-path-walker)
+;;; x-path-walker.el ends here
--- /dev/null
+# xpathwalker
+
+has_legs = False
--- /dev/null
+import sys
+
+from xpathwalker.args import parse_args
+
+
+##########################################################################
+# Main #
+##########################################################################
+
+def main(argv):
+ parse_args(argv)
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
+
+# Bench:
+# wc -l
+# 21302 /mnt/VM/home/lompik/Programmation/Web/d3_Map_GOM/Data/fldunit.json
+# with multiproc:
+# # string | .features[99].properties.UNIT_AGT_N
+# # string | .features[99].properties.VERSION_DA
+# # string | .features[99].type
+# # string | .features[9].geometry.type
+# # string | .features[9].properties.OUTLINE_TY
+# # string | .features[9].properties.SDE_TEXT_L
+# # string | .features[9].properties.UNIT_AGT_1
+# # string | .features[9].properties.UNIT_AGT_N
+# # string | .features[9].properties.VERSION_DA
+# # string | .features[9].type
+# # string | .type
+# # ./xmlxpath.py -a -M 374.99s user 29.73s system 569% cpu 1:11.05 total
+
+# without multiproc:
+# # array | .features[9].geometry.coordinates[1][0][7]
+# # number | .features[9].geometry.coordinates[1][0][7][0]
+# # number | .features[9].geometry.coordinates[1][0][7][1]
+# # array | .features[9].geometry.coordinates[1][0][8]
+# # number | .features[9].geometry.coordinates[1][0][8][0]
+# # number | .features[9].geometry.coordinates[1][0][8][1]
+# # array | .features[9].geometry.coordinates[1][0][9]
+# # number | .features[9].geometry.coordinates[1][0][9][0]
+# # number | .features[9].geometry.coordinates[1][0][9][1]
+# # string | .features[9].geometry.type
+# # object | .features[9].properties
+# # string | .features[9].properties.OUTLINE_TY
+# # string | .features[9].properties.SDE_TEXT_L
+# # number | .features[9].properties.SEQUENCE_N
+# # number | .features[9].properties.SHAPE_Area
+# # number | .features[9].properties.SHAPE_Leng
+# # number | .features[9].properties.SN_UNIT_AL
+# # number | .features[9].properties.SN_UNIT_OU
+# # string | .features[9].properties.UNIT_AGT_1
+# # string | .features[9].properties.UNIT_AGT_N
+# # string | .features[9].properties.VERSION_DA
+# # string | .features[9].type
+# # string | .type
+# # ./xmlxpath.py -a 241.04s user 13.05s system 93% cpu 4:31.48 total
--- /dev/null
+from argparse import ArgumentParser
+import os
+import sys
+
+exts_mode = {'.html': "HTML", '.html': 'HTML', '.xml': 'XML', '.json': 'JSON', '.yaml': 'YAML'}
+
+def parse_args(argv):
+ """parse command line arguments
+
+ :param argv:
+ :returns: Processor class
+ :rtype:
+
+ """
+ default_separator = "."
+
+ parser = ArgumentParser(description="Xpath helper")
+ parser.add_argument("inputfile",
+ help="Input FILE", type=str)
+ parser.add_argument("-x", "--xpath", dest='xpath',
+ help="Returns line number corresponding to xpath from FILE", type=str)
+ parser.add_argument("-m", "--mode", dest='mode',
+ help="Language type", type=str, choices=('XML', 'HTML', 'JSON'))
+ parser.add_argument("-a", dest='verbosity', action="store_true", default=False,
+ help="Increase verbosity")
+ parser.add_argument("-M", dest='multiproc', action="store_true", default=False,
+ help="Use multiple process when possible")
+ parser.add_argument("-j", dest='use_jq', action="store_true", default=False,
+ help="JSON: Use jq for processing")
+ parser.add_argument("-F", dest='separator', default=default_separator, type=str,
+ help="Path elements separator")
+ args = parser.parse_args(argv)
+
+ inputfile = args.inputfile
+ proc = False
+ if(not os.path.isfile(inputfile)):
+ sys.exit(1)
+
+ ext = os.path.splitext(inputfile)[1].lower()
+
+ if(args.mode is None):
+ if ext not in exts_mode.keys():
+ print("Unrecognized file format.")
+ sys.exit(1)
+ else:
+ args.mode = exts_mode[ext]
+
+ if(args.mode == 'HTML'):
+ from xpathwalker.lxml_Processor import HTML_Processor
+ proc = HTML_Processor()
+ if args.xpath:
+ args.xpath = "/"+args.xpath
+ elif(args.mode == 'XML'):
+ from xpathwalker.lxml_Processor import XML_Processor
+ proc = XML_Processor()
+ if args.xpath:
+ args.xpath = "/"+args.xpath
+ elif(args.mode == 'JSON'):
+ if(args.use_jq):
+ from xpathwalker.json_jq_Processor import JSON_jq_Processor
+ proc = JSON_jq_Processor()
+ else:
+ from xpathwalker.json_Processor import JSON_Processor
+ proc = JSON_Processor()
+ else:
+ print("Error in arg parsing")
+ sys.exit(2)
+
+ proc.separator = args.separator
+ proc.parse_file(inputfile)
+
+ if(args.xpath is not None):
+ print(proc.get_path_line_number(args.xpath, args.verbosity))
+ else:
+ print("\n".join(proc.list_paths(args.verbosity)))
--- /dev/null
+from xpathwalker.utils import list_compare_idx
+from xpathwalker.processor import Processor
+from xpathwalker.utils import traverse_and_remove_path,\
+ get_dict_from_path, get_class
+import json
+from collections import MutableSequence, MutableMapping
+from collections import OrderedDict
+
+##########################################################################
+# JSON / Python #
+##########################################################################
+
+
+class JSON_Processor(Processor):
+ file_line_number = 0
+
+ def isValidJsonShorthandPropertyName(self, key):
+ if not key[0].isalpha() or not key.isalnum():
+ return('["' + key + '"]')
+ else:
+ return(self.separator + key)
+
+
+ def paths(self, obj, dirs=True, leaves=True, path='', skip=False, allow_empty_string_keys=False):
+ """Yield all paths of the object.
+ Arguments:
+ obj -- An object to get paths from.
+ Keyword Arguments:
+ dirs -- Yield intermediate paths.
+ leaves -- Yield the paths with leaf objects.
+ path -- A list of keys representing the path.
+ skip -- Skip special keys beginning with '+'.
+ """
+ if isinstance(obj, MutableMapping):
+ # Python 3 support
+ # if PY3:
+ iteritems = obj.items()
+
+ for (k, v) in iteritems:
+ if issubclass(k.__class__, (str)):
+ if (not k) and (not allow_empty_string_keys):
+ print("Empty string keys not allowed without "
+ "dpath.options.ALLOW_EMPTY_STRING_KEYS=True")
+ elif (skip and k[0] == '+'):
+ continue
+ newpath = path + self.isValidJsonShorthandPropertyName(str(k))
+ # validate(newpath)
+ if dirs:
+ yield {newpath: {"class": v.__class__}}
+ for child in self.paths(v, dirs, leaves, newpath, skip):
+ yield child
+
+ elif isinstance(obj, MutableSequence):
+ for (i, v) in enumerate(obj):
+ newpath = path + "[%s]" % i
+ if issubclass(i.__class__, int):
+ newpath = path + "[%d]" % i
+ if dirs:
+ yield {newpath: {"class": v.__class__}}
+ for child in self.paths(obj[i], dirs, leaves, newpath, skip):
+ yield child
+ elif leaves:
+ yield path + [[obj, obj.__class__]]
+ elif not dirs:
+ yield path
+
+ def serialize_json(self):
+ res = OrderedDict()
+ for x in self.paths(self.parsed, leaves=False):
+ res.update(x)
+ return(res)
+
+ def list_paths(self, with_attrs=True):
+ json_paths = self.serialize_json()
+ if(with_attrs):
+ return(["%6s | %s" % (get_class(json_paths[x]["class"]), x) for x in json_paths.keys()])
+ else:
+ return([x for x in json_paths.keys()])
+
+ def get_path_line_number(self, path, with_attrs=False):
+ path = path.split("| ")[1] if (with_attrs is True) else path
+
+ orig = self.parsed
+ path = get_dict_from_path(path, self.separator)
+ res = traverse_and_remove_path(orig, path)
+ orig = json.dumps(orig, indent=4).split("\n")
+
+ if(len(orig) != self.file_line_number):
+ raise ValueError("This json file formatting is not compatible. Please run `python -m json.tool file`")
+
+ with_del = json.dumps(res, indent=4).split("\n")
+ return(list_compare_idx(orig, with_del) + 1)
+
+ def parse_file(self, file):
+ with open(file) as f:
+ self.file_line_number = len(f.readlines())
+ f.seek(0)
+ self.parsed = json.load(f, object_pairs_hook=OrderedDict)
--- /dev/null
+import subprocess
+import sys
+import os
+from tempfile import NamedTemporaryFile
+from xpathwalker.utils import list_compare_idx
+from xpathwalker.processor import Processor
+
+##########################################################################
+# JSON #
+##########################################################################
+
+jq_command = """def safeLookupValue:
+ if type != "string" then
+ tostring
+ else
+ .
+ end;
+\
+def arrayToLookup:
+ map(safeLookupValue)
+ | reduce .[] as $exists (
+ {};
+ . + {
+ ($exists): null
+ }
+ );
+\
+def lookup(value):
+ (value | safeLookupValue) as $value
+ | has($value);
+\
+def isWhitelisted(whitelist):
+ whitelist as $whitelist
+ | explode
+ | map(
+ . as $charCode
+ | $whitelist
+ | lookup($charCode)
+ )
+ | all;
+\
+def isNumeric:
+ ("0123456789" | explode | arrayToLookup) as $digits
+ | isWhitelisted($digits);
+\
+def isAlpha:
+ ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | explode | arrayToLookup) as $letters
+ | isWhitelisted($letters);
+\
+def isAlphanumeric:
+ ("0123456789" | explode | arrayToLookup) as $digits
+ | ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | explode | arrayToLookup) as $letters
+ | ("_" | explode | arrayToLookup) as $special
+ | ($digits + $letters + $special) as $alphanumeric
+ | isWhitelisted($alphanumeric);
+\
+def isValidJsonShorthandPropertyName:
+ (.[0:1] | isAlpha | not) or (isAlphanumeric | not);
+\
+[
+ path(..)
+ | map(
+ if type == "number" then
+ ["[",(.|tostring),"]"]|join("")
+ else
+ tostring
+ | if isValidJsonShorthandPropertyName then
+ "[\\"\\(.)\\"]"
+ else
+ .
+ end
+ end
+ )
+ | join(".")
+ | split("." + "[")
+ | join("[")
+]
+| unique
+| map("." + .)
+| .[]"""
+
+jq_exec = ["jq", "--raw-output"]
+
+def jq_proc(args):
+ with NamedTemporaryFile() as f:
+ try:
+ proc = subprocess.check_call(args,
+ stdout=f,
+ stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError:
+ print(proc.stderr.fseek(0).read().decode('utf-8'))
+ sys.exit(1)
+ f.seek(0)
+ return f.read().decode('utf-8').split('\n')
+
+
+class JSON_jq_Processor(Processor):
+ file = ""
+
+ def list_paths(self, with_attrs=True, multiproc=False):
+ if(not os.path.isfile(self.file)):
+ sys.exit(1)
+ proc = jq_proc(args=jq_exec + [jq_command,
+ self.file])
+ paths = [x for x in proc if x != '']
+ res = []
+ if(with_attrs is False):
+ res = paths
+ elif with_attrs is True:
+ if multiproc is False:
+ for x in paths:
+ proc = jq_proc(args=jq_exec + ["%s|type" % x,
+ self.file])
+ type = proc[0]
+ res.append("%6s | %s" % (type[0:6], x))
+ else:
+ def get_types(q, x, file):
+ proc = jq_proc(args=jq_exec + ["%s|type" % x,
+ self.file])
+ type = proc[0]
+ q.put("%6s | %s" % (type[0:6], x))
+ import queue
+ import threading
+ q = queue.PriorityQueue()
+ for x in paths:
+ t = threading.Thread(target=get_types, args=(q, x, self.file))
+ t. start()
+ while(q.qsize() > 0):
+ res.append(q.get())
+ return(res)
+
+ def get_path_line_number(self, path, with_attrs=False):
+ path = path.split("| ")[1] if (with_attrs is True) else path
+ proc = jq_proc(args=jq_exec + ['.',
+ self.file])
+ orig = proc
+ proc = jq_proc(args=jq_exec + ['del(%s)' % path,
+ self.file])
+ with_del = proc
+ return(list_compare_idx(orig, with_del) + 1)
+
+ def parse_file(self, file):
+ if(not os.path.isfile(file)):
+ print("File %s: Not Found" % (file))
+ sys.exit(1)
+ self.file = file
--- /dev/null
+from xpathwalker.processor import Processor
+import os
+import sys
+import lxml.etree
+import lxml.html
+
+
+class lxmlProcessor(Processor):
+ """ [X/HT]ML processor
+ """
+ def list_paths(self, with_attrs=False):
+ if(not self.parsed):
+ raise ValueError("No data in parsed")
+ parsed = self.parsed
+ res = []
+ for el in parsed.xpath("//*"):
+ if(with_attrs):
+ if el.keys() is None:
+ res.append("/%s | %s | %s" % (parsed.getelementpath(el), ",".join(el.keys())))
+ else:
+ res.append("/%s | %s" % (parsed.getelementpath(el),
+ ",".join(["%s: %s" % (attr, el.get(key=attr)) for attr in el.keys()])))
+ else:
+ res.append("/%s" % (parsed.getelementpath(el)))
+ return(res)
+
+ def handle_namespace_in_xpath(self, xpath):
+ closinp = len(xpath.split("}"))
+ if(closinp > 1 and closinp != len(xpath.split("}"))):
+ return xpath
+ import re
+ import string
+ insidep = r"(?<={)[^}]*"
+ namesspaces = re.findall(insidep, xpath)
+ nsdict = {}
+
+ def get_letters(matchobj):
+ get_letters.counter += 1
+ id = string.ascii_letters[get_letters.counter]
+ nsdict.update({id: namesspaces[get_letters.counter]})
+ return id + ":"
+ get_letters.counter = -1
+ par = re.compile(r"{"+insidep+r"}")
+ xpath = par.sub(get_letters, xpath)
+ return (xpath, {"namespaces": nsdict})
+
+ def get_path_line_number(self, xpath, with_attrs=False):
+ if(not self.parsed):
+ raise ValueError("No data in parsed")
+
+ parsed = self.parsed
+ xpath = xpath.split(" | ")[0] if (with_attrs is True or xpath.find(" | ") > -1) else xpath
+ res = 0
+ matchs = []
+
+ if(xpath.find("{") > -1): # Handle namespace cases
+ xpath, args = self.handle_namespace_in_xpath(xpath)
+ matchs = parsed.xpath(xpath, **args)
+ else:
+ matchs = parsed.xpath(xpath)
+ if(len(matchs) > 0):
+ res = matchs[0].sourceline
+ return(res)
+
+class HTML_Processor(lxmlProcessor):
+ def parse_file(self, file):
+ """HTML file parse from lxml
+
+ :param file: file path (string)
+ :returns: null
+ :rtype:
+
+ """
+ self.parsed = lxml.html.parse(file)
+
+class XML_Processor(lxmlProcessor):
+ def parse_file(self, file):
+ """HTML file parse from lxml
+
+ :param file: file path (string)
+ :returns: null
+ :rtype:
+
+ """
+ self.parsed = lxml.etree.parse(file)
--- /dev/null
+
+
+class Processor:
+ """An abstract object class to handle the parsing/procesing of files
+
+ """
+ separator = "."
+
+ def __init__(self):
+ super(Processor, self).__init__()
+
+ def list_paths(self, parsed, with_attrs=False):
+ """Return array of path separated by the separator
+
+ :param parsed: A python type object
+ :param with_attrs: List additional attributes
+ :returns: array of paths
+ :rtype: array
+
+ """
+ pass
+
+ def get_path_line_number(self, path, with_attrs=False):
+ """Returns the line number from the file corresponding `path`
+
+ :param path: Path seeked
+ :param with_attrs: `True` if `path` is formatted with additional attributes
+ :returns: line numer corresponding to path
+ :rtype: int
+
+ """
+ pass
+
+ def parse_file(self, file):
+ """Translate the `file` into python object
+
+ :param file: File path
+ :returns: Nothing. insternal variable updated.
+ :rtype:
+
+ """
+ pass
--- /dev/null
+from collections import OrderedDict
+
+##########################################################################
+# Utils #
+##########################################################################
+
+def get_class(obj):
+ name = obj.__name__
+ if(name == "OrderedDict"):
+ return("object")
+ elif(name == "NoneType"):
+ return("null")
+ return(name)
+
+def list_compare_idx(list1, list2):
+ index = 0
+ for idx, origline in enumerate(list1):
+ if(list2[idx] != origline):
+ if((list2[idx]+",") != origline):
+ index = idx
+ break
+ return(index)
+
+def split_path(path, separator, inside_special=False):
+ first_match = '"]' if inside_special else '["'
+ i = path.find(first_match)
+ if i != -1 and inside_special is False:
+ if(path[0:i] != ''):
+ return (split_path(path[0:i],
+ separator=separator,
+ inside_special=False) +
+ split_path(path[i+len('["'):],
+ separator=separator,
+ inside_special=True))
+ else:
+ return (split_path(path[i+len('["'):],
+ separator=separator,
+ inside_special=True))
+ if i != -1 and inside_special is True:
+ if(path[i+len('["'):] != ''):
+ return ([path[:i], "special"],) + split_path(path[i+len('["'):],
+ separator=separator,
+ inside_special=False)
+ else:
+ return ([path[:i], "special"],)
+ else:
+ i = path.find(separator)
+ if i == -1:
+ return (path,)
+ else:
+ if(i != 0):
+ return (path[:i],) + split_path(path[i+1:], separator)
+ else:
+ return split_path(path[i+1:], separator)
+
+
+def get_dict_from_path(path, separator):
+ path = split_path(path, separator)
+
+ res = []
+ for x in path:
+ if not isinstance(x, list):
+ pos = x.find("[")
+ while pos != -1:
+ if pos == 0:
+ i1 = x.find("]")
+ x1 = int(x[0:i1].replace("[", "", 1).replace("]", "", 1))
+ x = x[i1+1:]
+ res.append(x1)
+ else:
+ res.append(x[0:pos])
+ x = x[pos:]
+ pos = x.find("[")
+ else:
+ res.append(x[0])
+ return(res)
+
+def traverse_and_remove_path(obj, path=None, match="First"):
+ if path is None:
+ path = []
+ if isinstance(obj, dict):
+ res = OrderedDict()
+ for k, v in obj.items():
+ cmatch = False
+ if(match and len(path) > 0 and path[0] == k):
+ cmatch = True
+ res.update({k: traverse_and_remove_path(v, path=path[1:], match=cmatch)})
+ if(len(path) == 1 and path[0] in res.keys() and match):
+ del res[path[0]]
+ return res
+ elif isinstance(obj, list):
+ res = []
+ for i, elem in enumerate(obj):
+ cmatch = False
+ if(match and len(path) >= 1 and isinstance(path[0], int) and path[0] < len(obj) and i == path[0]):
+ cmatch = True
+ res.append(traverse_and_remove_path(elem, path=path[1:], match=cmatch))
+ if(len(path) == 1 and isinstance(path[0], int) and path[0] < len(res) and match):
+ res.pop(path[0])
+ return res
+ else:
+ return obj # no container, just values (str, int, float)
--- /dev/null
+##########################################################################
+# YAML #
+##########################################################################
+import yaml
+from xpathwalker.utils import list_compare_idx
+from xpathwalker.utils import traverse_and_remove_path, get_dict_from_path, get_class, paths
+from collections import OrderedDict
+import os
+
+def parse_yaml(file):
+ with open(file) as f:
+ yaml_data = yaml.load(f)
+ res = OrderedDict()
+ for x in paths(yaml_data, leaves=False):
+ res.update(x)
+ return(res)
+
+def get_yaml_paths(file, include_type=True): # XXX: ordering is lost
+ yaml_paths = parse_yaml(file)
+ if(include_type is True):
+ return(["%s | %s" % (get_class(yaml_paths[x]['class']), x) for x in yaml_paths.keys()])
+ else:
+ return(yaml_paths.keys())
+
+def get_yaml_path_line_python(file, path, include_type=False):
+ if(not os.path.isfile(file)):
+ sys.exit(1)
+ path = path.split("| ")[1] if (include_type is True) else path
+ orig = ""
+ with open(file) as f:
+ orig = yaml.load(f)
+ path = get_dict_from_path(path)
+ res = traverse_and_remove_path(orig, path)
+ yaml_dump_options = {"indent": 4, "default_flow_style": False, "explicit_start": False}
+ orig = yaml.dump(orig, **yaml_dump_options).split("\n")
+ with_del = yaml.dump(dict(res), **yaml_dump_options).split("\n")
+ return(list_compare_idx(orig, with_del) + 1)