XML VFS 0.2 - show attributes as a text file
[extfs.d.git] / xml
1 #! /usr/bin/env python
2 """XML Virtual FileSystem for Midnight Commander
3
4 The script requires Midnight Commander 3.1+
5 (http://www.midnight-commander.org/), Python 2.4+ (http://www.python.org/).
6
7 For mc 4.7+ put the script in $HOME/.mc/extfs.d.
8 For older versions put it in /usr/[local/][lib|share]/mc/extfs
9 and add a line "xml" to the /usr/[local/][lib|share]/mc/extfs/extfs.ini.
10 Make the script executable.
11
12 Run this "cd" command in the Midnight Commander (in the "bindings" file the
13 command is "%cd"): cd file.xml#xml, where "file.xml" is the name of your xml
14 file.
15
16 The VFS represents tags as directories; the directories are numbered to
17 distinguish tags with the same name; also numbering helps to sort tags by their
18 order in XML instead of sorting them by name. Attributes, text nodes and
19 comments are represented as text files; attributes are shown in a file named
20 "attributes", attributes are listed in the file as name=value lines (I
21 deliberately ignore a small chance there is a newline character in values). The
22 filesystem is read-only.
23
24 The VFS was inspired by a FUSE xmlfs: https://github.com/halhen/xmlfs
25
26 """
27
28 __version__ = "0.2.0"
29 __author__ = "Oleg Broytman <phd@phdru.name>"
30 __copyright__ = "Copyright (C) 2013 PhiloSoft Design"
31 __license__ = "GPL"
32
33 import math
34 import sys
35 import xml.dom.minidom
36
37 try:
38    import locale
39    use_locale = True
40 except ImportError:
41    use_locale = False
42
43 if use_locale:
44    # Get the default charset.
45    try:
46       lcAll = locale.getdefaultlocale()
47    except locale.Error, err:
48       print >>sys.stderr, "WARNING:", err
49       lcAll = []
50
51    if len(lcAll) == 2:
52       default_encoding = lcAll[1]
53    else:
54       try:
55          default_encoding = locale.getpreferredencoding()
56       except locale.Error, err:
57          print >>sys.stderr, "WARNING:", err
58          default_encoding = sys.getdefaultencoding()
59 else:
60    default_encoding = sys.getdefaultencoding()
61
62 import logging
63 logger = logging.getLogger('xml-mcextfs')
64 log_err_handler = logging.StreamHandler(sys.stderr)
65 logger.addHandler(log_err_handler)
66 logger.setLevel(logging.INFO)
67
68 if len(sys.argv) < 3:
69     logger.critical("""\
70 XML Virtual FileSystem for Midnight Commander version %s
71 Author: %s
72 %s
73
74 This is not a program. Put the script in $HOME/.mc/extfs.d or
75 /usr/[local/][lib|share]/mc/extfs. For more information read the source!""",
76    __version__, __author__, __copyright__
77 )
78     sys.exit(1)
79
80
81 locale.setlocale(locale.LC_ALL, '')
82
83 def _attrs2text(attrs):
84     attrs = [attrs.item(i) for i in range (attrs.length)]
85     return '\n'.join(["%s=%s" %
86         (a.name.encode(default_encoding, "replace"),
87         a.value.encode(default_encoding, "replace"))
88         for a in attrs])
89
90 def _list(node, path=''):
91     childNodes = node.childNodes
92     n = 0
93     for element in childNodes:
94         if element.localName:
95             n += 1
96     if n:
97         width = int(math.log10(n))+1
98         template = "%%0%dd" % width
99     else:
100         template = "%d"
101     n = 0
102     for element in childNodes:
103         if element.localName:
104             n += 1
105             if path:
106                 subpath = '%s/%s %s' % (path, template % n, element.localName)
107             else:
108                 subpath = '%s %s' % (template % n, element.localName)
109             subpath_encoded = subpath.encode(default_encoding, "replace")
110             print "dr--r--r-- 1 user group 0 Jan 1 00:00 %s" % subpath_encoded
111             attrs = element.attributes
112             if attrs:
113                 attr_text = _attrs2text(attrs)
114                 print "-r--r--r-- 1 user group %d Jan 1 00:00 %s/attributes" % (
115                     len(attr_text), subpath_encoded)
116             _list(element, subpath)
117
118 def mcxml_list():
119     """List the entire VFS"""
120
121     dom = xml.dom.minidom.parse(sys.argv[2])
122     _list(dom)
123
124
125 def _get_child_node(node, i):
126     n = 0
127     for element in node.childNodes:
128         if element.localName:
129             n += 1
130             if n == i:
131                 return element
132     xml_error('There are less than %d nodes' % i)
133
134 def mcxml_copyout():
135     """Extract a file from the VFS"""
136
137     node = xml.dom.minidom.parse(sys.argv[2])
138     xml_filename = sys.argv[3]
139     real_filename = sys.argv[4]
140
141     for path_comp in xml_filename.split('/'):
142         if ' ' in path_comp:
143             i = int(path_comp.split(' ', 1)[0])
144             node = _get_child_node(node, i)
145         elif path_comp == 'attributes':
146             break
147         else:
148             xml_error('Unknown file')
149
150     if path_comp == 'attributes':
151         attrs = node.attributes
152         if attrs:
153             text = _attrs2text(attrs)
154         else:
155             xml_error('There are no attributes')
156
157     outfile = open(real_filename, 'w')
158     outfile.write(text)
159     outfile.close()
160
161
162 def mcxml_copyin():
163     """Put a file to the VFS"""
164     sys.exit("XML VFS doesn't support adding files (read-only filesystem)")
165
166 def mcxml_rm():
167     """Remove a file from the VFS"""
168     sys.exit("XML VFS doesn't support removing files/directories (read-only filesystem)")
169
170 mcxml_rmdir = mcxml_rm
171
172 def mcxml_mkdir():
173     """Create a directory in the VFS"""
174     sys.exit("XML VFS doesn't support creating directories (read-only filesystem)")
175
176
177 def xml_error(error_str):
178     logger.critical("Error walking XML file: %s", error_str)
179     sys.exit(1)
180
181 command = sys.argv[1]
182 procname = "mcxml_" + command
183
184 g = globals()
185 if not g.has_key(procname):
186     logger.critical("Unknown command %s", command)
187     sys.exit(1)
188
189 try:
190     g[procname]()
191 except SystemExit:
192     raise
193 except:
194     logger.exception("Error during run")