forked from alek-sys/sublimetext_indentxml
-
Notifications
You must be signed in to change notification settings - Fork 0
/
indentxml.py
117 lines (98 loc) · 3.97 KB
/
indentxml.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import sublime
import sublime_plugin
import re
import json
from xml.dom.minidom import *
from os.path import basename
class BaseIndentCommand(sublime_plugin.TextCommand):
def __init__(self, view):
self.view = view
self.language = self.get_language()
def get_language(self):
syntax = self.view.settings().get('syntax')
language = basename(syntax).replace('.tmLanguage', '').lower() if syntax != None else "plain text"
return language
def check_enabled(self, lang):
return True
def is_enabled(self):
"""
Enables or disables the 'indent' command.
Command will be disabled if there are currently no text selections and current file is not 'XML' or 'Plain Text'.
This helps clarify to the user about when the command can be executed, especially useful for UI controls.
"""
if self.view == None:
return False
return self.check_enabled(self.get_language())
def run(self, edit):
"""
Main plugin logic for the 'indent' command.
"""
view = self.view
regions = view.sel()
# if there are more than 1 region or region one and it's not empty
if len(regions) > 1 or not regions[0].empty():
for region in view.sel():
if not region.empty():
s = view.substr(region).strip()
s = self.indent(s)
view.replace(edit, region, s)
else: #format all text
alltextreg = sublime.Region(0, view.size())
s = view.substr(alltextreg).strip()
s = self.indent(s)
view.replace(edit, alltextreg, s)
class AutoIndentCommand(BaseIndentCommand):
def get_text_type(self, s):
language = self.language
if language == 'xml':
return 'xml'
if language == 'json':
return 'json'
if language == 'plain text' and s:
if s[0] == '<':
return 'xml'
if s[0] == '{' or s[0] == '[':
return 'json'
return 'notsupported'
def indent(self, s):
text_type = self.get_text_type(s)
if text_type == 'xml':
command = IndentXmlCommand(self.view)
if text_type == 'json':
command = IndentJsonCommand(self.view)
if text_type == 'notsupported':
return s
return command.indent(s)
def check_enabled(self, lang):
return True
class IndentXmlCommand(BaseIndentCommand):
def indent(self, s):
# convert to utf
s = s.encode("utf-8")
xmlheader = re.compile(b"<\?.*\?>").match(s)
# convert to plain string without indents and spaces
s = re.compile(b'>\s+([^\s])', re.DOTALL).sub(b'>\g<1>', s)
# replace tags to convince minidom process cdata as text
s = s.replace(b'<![CDATA[', b'%CDATAESTART%').replace(b']]>', b'%CDATAEEND%')
try:
s = parseString(s).toprettyxml()
except Exception as e:
sublime.active_window().run_command("show_panel", {"panel": "console", "toggle": True})
raise e
# remove line breaks
s = re.compile('>\n\s+([^<>\s].*?)\n\s+</', re.DOTALL).sub('>\g<1></', s)
# restore cdata
s = s.replace('%CDATAESTART%', '<![CDATA[').replace('%CDATAEEND%', ']]>')
# remove xml header
s = s.replace("<?xml version=\"1.0\" ?>", "").strip()
if xmlheader:
s = xmlheader.group().decode("utf-8") + "\n" + s
return s
def check_enabled(self, language):
return ((language == "xml") or (language == "plain text"))
class IndentJsonCommand(BaseIndentCommand):
def check_enabled(self, language):
return ((language == "json") or (language == "plain text"))
def indent(self, s):
parsed = json.loads(s)
return json.dumps(parsed, sort_keys=True, indent=4, separators=(',', ': '))