summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Jehannet <julien.jehannet@logilab.fr>2009-09-15 15:03:37 +0200
committerJulien Jehannet <julien.jehannet@logilab.fr>2009-09-15 15:03:37 +0200
commit7036bbe00c6c699eac86a65467c583ee901d754d (patch)
tree8da3fb9383205301b4d485604f0778c632020a4a
parent028ddb178ec0276bd03b4b9694c3faf1d715a9dd (diff)
parent9fa76459138f861baff69ad0a58ff078a4cca2da (diff)
downloadlogilab-common-7036bbe00c6c699eac86a65467c583ee901d754d.tar.gz
(merge)
-rw-r--r--ChangeLog7
-rw-r--r--__pkginfo__.py5
-rw-r--r--adbh.py4
-rw-r--r--configuration.py16
-rw-r--r--debian/changelog6
-rw-r--r--decorators.py1
-rw-r--r--graph.py9
-rw-r--r--html.py125
-rw-r--r--logging_ext.py2
-rw-r--r--test/unittest_html.py59
-rw-r--r--test/unittest_testlib.py16
-rw-r--r--test/unittest_xmlutils.py58
-rw-r--r--textutils.py18
-rw-r--r--xmlutils.py44
14 files changed, 354 insertions, 16 deletions
diff --git a/ChangeLog b/ChangeLog
index 9f2912f..2a65210 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,13 @@
ChangeLog for logilab.common
============================
+--
+ * configuration: fix #8849 Using plugins, options and .pylintrc crashes PyLint
+
+2009-08-26 -- 0.45.0
+ * added function for parsing XML processing instructions
+
+
2009-08-07 -- 0.44.0
* remove code deprecated for a while now
diff --git a/__pkginfo__.py b/__pkginfo__.py
index b093a2b..148b3d8 100644
--- a/__pkginfo__.py
+++ b/__pkginfo__.py
@@ -8,7 +8,7 @@ __docformat__ = "restructuredtext en"
distname = 'logilab-common'
modname = 'common'
-numversion = (0, 44, 0)
+numversion = (0, 45, 0)
version = '.'.join([str(num) for num in numversion])
license = 'GPL'
@@ -27,12 +27,13 @@ modules,
* writing interactive command line tools
* manipulation files and character strings
* interfacing to OmniORB
- * generating of SQL queries
+ * generating SQL queries
* running unit tests
* manipulating tree structures
* accessing RDBMS (currently postgreSQL, mysql and sqlite)
* generating text and HTML reports
* logging
+ * parsing XML processing instructions
* more...
"""
diff --git a/adbh.py b/adbh.py
index 0636bbe..b0047b7 100644
--- a/adbh.py
+++ b/adbh.py
@@ -318,7 +318,7 @@ class _PGAdvFuncHelper(_GenericAdvFuncHelper):
cmd.append('--username=%s' % dbuser)
if not keepownership:
cmd.append('--no-owner')
- cmd.append('--file=%s' % backupfile)
+ cmd.append('--file="%s"' % backupfile)
cmd.append(dbname)
return ' '.join(cmd)
@@ -337,7 +337,7 @@ class _PGAdvFuncHelper(_GenericAdvFuncHelper):
cmd.append('--dbname %s' % dbname)
if not keepownership:
cmd.append('--no-owner')
- cmd.append(backupfile)
+ cmd.append('"%s"' % backupfile)
cmds.append(' '.join(cmd))
return cmds
diff --git a/configuration.py b/configuration.py
index f6dec1b..969e36d 100644
--- a/configuration.py
+++ b/configuration.py
@@ -98,7 +98,8 @@ import sys
import re
from os.path import exists, expanduser
from copy import copy
-from ConfigParser import ConfigParser, NoOptionError, NoSectionError
+from ConfigParser import ConfigParser, NoOptionError, NoSectionError, \
+ DuplicateSectionError
from logilab.common.compat import set
from logilab.common.textutils import normalize_text, unquote
@@ -358,6 +359,7 @@ class OptionsManagerMixIn(object):
self._all_options = {}
self._short_options = {}
self._nocallback_options = {}
+ self._mygroups = set()
# verbosity
self.quiet = quiet
@@ -400,7 +402,15 @@ class OptionsManagerMixIn(object):
"""
# add section to the config file
if group_name != "DEFAULT":
- self._config_parser.add_section(group_name)
+ try:
+ self._config_parser.add_section(group_name)
+ except DuplicateSectionError:
+ # section may be implicitly added by reading the configuration
+ # file (see http://www.logilab.org/ticket/8849 for description
+ # of a case where this may happen)
+ if group_name in self._mygroups:
+ raise
+ self._mygroups.add(group_name)
# add option group to the command line parser
if options:
group = OptionGroup(self._optik_parser,
@@ -422,6 +432,8 @@ class OptionsManagerMixIn(object):
else:
opt_dict['action'] = 'callback'
opt_dict['callback'] = self.cb_set_provider_option
+ if 'choices' in opt_dict:
+ opt_dict['help'] += ': %s' % '|'.join(opt_dict['choices'])
# default is handled here and *must not* be given to optik if you
# want the whole machinery to work
if 'default' in opt_dict:
diff --git a/debian/changelog b/debian/changelog
index edee802..8460636 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+logilab-common (0.45.0-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Olivier Cayrol <olivier.cayrol@logilab.fr> Wed, 26 Aug 2009 17:40:00 +0200
+
logilab-common (0.44.0-1) unstable; urgency=low
* new upstream release
diff --git a/decorators.py b/decorators.py
index d7768a0..f5278cc 100644
--- a/decorators.py
+++ b/decorators.py
@@ -81,7 +81,6 @@ def copy_cache(obj, funcname, cacheobj):
except KeyError:
pass
-
class wproperty(object):
"""Simple descriptor expecting to take a modifier function as first argument
and looking for a _<function name> to retrieve the attribute.
diff --git a/graph.py b/graph.py
index df5d838..c81887e 100644
--- a/graph.py
+++ b/graph.py
@@ -12,6 +12,8 @@ __metaclass__ = type
import os.path as osp
import os
+import subprocess
+import sys
import tempfile
def escape(value):
@@ -80,12 +82,15 @@ class DotBackend:
storedir, basename, target = target_info_from_filename(outputfile)
if target != "dot":
pdot, dot_sourcepath = tempfile.mkstemp(".dot", name)
+ os.close(pdot)
else:
dot_sourcepath = osp.join(storedir, dotfile)
else:
target = 'png'
pdot, dot_sourcepath = tempfile.mkstemp(".dot", name)
ppng, outputfile = tempfile.mkstemp(".png", name)
+ os.close(pdot)
+ os.close(ppng)
pdot = open(dot_sourcepath,'w')
if isinstance(self.source, unicode):
pdot.write(self.source.encode('UTF8'))
@@ -93,8 +98,8 @@ class DotBackend:
pdot.write(self.source)
pdot.close()
if target != 'dot':
- os.system('%s -T%s %s -o%s' % (self.renderer, target,
- dot_sourcepath, outputfile))
+ subprocess.call('%s -T%s %s -o%s' % (self.renderer, target,
+ dot_sourcepath, outputfile), shell=True)
os.unlink(dot_sourcepath)
return outputfile
diff --git a/html.py b/html.py
new file mode 100644
index 0000000..5c949ab
--- /dev/null
+++ b/html.py
@@ -0,0 +1,125 @@
+"""render a tree in HTML.
+
+:copyright: 2000-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: General Public License version 2 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+
+def render_HTML_tree(tree, selected_node=None, render_node=None, caption=None):
+ """
+ Generate a pure HTML representation of a tree given as an instance
+ of a logilab.common.tree.Node
+
+ selected_node is the currently selected node (if any) which will
+ have its surrounding <div> have id="selected" (which default
+ to a bold border libe with the default CSS).
+
+ render_node is a function that should take a Node content (Node.id)
+ as parameter and should return a string (what will be displayed
+ in the cell).
+
+ Warning: proper rendering of the generated html code depends on html_tree.css
+ """
+ tree_depth = tree.depth_down()
+ if render_node is None:
+ render_node = str
+
+ # helper function that build a matrix from the tree, like:
+ # +------+-----------+-----------+
+ # | root | child_1_1 | child_2_1 |
+ # | root | child_1_1 | child_2_2 |
+ # | root | child_1_2 | |
+ # | root | child_1_3 | child_2_3 |
+ # | root | child_1_3 | child_2_4 |
+ # +------+-----------+-----------+
+ # from:
+ # root -+- child_1_1 -+- child_2_1
+ # | |
+ # | +- child_2_2
+ # +- child_1_2
+ # |
+ # +- child1_3 -+- child_2_3
+ # |
+ # +- child_2_2
+ def build_matrix(path, matrix):
+ if path[-1].is_leaf():
+ matrix.append(path[:])
+ else:
+ for child in path[-1].children:
+ build_matrix(path[:] + [child], matrix)
+
+ matrix = []
+ build_matrix([tree], matrix)
+
+ # make all lines in the matrix have the same number of columns
+ for line in matrix:
+ line.extend([None]*(tree_depth-len(line)))
+ for i in range(len(matrix)-1, 0, -1):
+ prev_line, line = matrix[i-1:i+1]
+ for j in range(len(line)):
+ if line[j] == prev_line[j]:
+ line[j] = None
+
+ # We build the matrix of link types (between 2 cells on a line of the matrix)
+ # link types are :
+ link_types = {(True, True, True ): 1, # T
+ (False, False, True ): 2, # |
+ (False, True, True ): 3, # + (actually, vert. bar with horiz. bar on the right)
+ (False, True, False): 4, # L
+ (True, True, False): 5, # -
+ }
+ links = []
+ for i, line in enumerate(matrix):
+ links.append([])
+ for j in range(tree_depth-1):
+ cell_11 = line[j] is not None
+ cell_12 = line[j+1] is not None
+ cell_21 = line[j+1] is not None and line[j+1].next_sibling() is not None
+ link_type = link_types.get((cell_11, cell_12, cell_21), 0)
+ if link_type == 0 and i > 0 and links[i-1][j] in (1, 2, 3):
+ link_type = 2
+ links[-1].append(link_type)
+
+
+ # We can now generate the HTML code for the <table>
+ s = u'<table class="tree">\n'
+ if caption:
+ s += '<caption>%s</caption>\n' % caption
+
+ for i, link_line in enumerate(links):
+ line = matrix[i]
+
+ s += '<tr>'
+ for j, link_cell in enumerate(link_line):
+ cell = line[j]
+ if cell:
+ if cell.id == selected_node:
+ s += '<td class="tree_cell" rowspan="2"><div id="selected" class="tree_cell">%s</div></td>' % (render_node(cell.id))
+ else:
+ s += '<td class="tree_cell" rowspan="2"><div class="tree_cell">%s</div></td>' % (render_node(cell.id))
+ else:
+ s += '<td rowspan="2">&nbsp;</td>'
+ s += '<td class="tree_cell_%d_1">&nbsp;</td>' % link_cell
+ s += '<td class="tree_cell_%d_2">&nbsp;</td>' % link_cell
+
+ cell = line[-1]
+ if cell:
+ if cell.id == selected_node:
+ s += '<td class="tree_cell" rowspan="2"><div id="selected" class="tree_cell">%s</div></td>' % (render_node(cell.id))
+ else:
+ s += '<td class="tree_cell" rowspan="2"><div class="tree_cell">%s</div></td>' % (render_node(cell.id))
+ else:
+ s += '<td rowspan="2">&nbsp;</td>'
+
+ s += '</tr>\n'
+ if link_line:
+ s += '<tr>'
+ for j, link_cell in enumerate(link_line):
+ s += '<td class="tree_cell_%d_3">&nbsp;</td>' % link_cell
+ s += '<td class="tree_cell_%d_4">&nbsp;</td>' % link_cell
+ s += '</tr>\n'
+
+ s += '</table>'
+ return s
diff --git a/logging_ext.py b/logging_ext.py
index 4fce3a7..5a27d05 100644
--- a/logging_ext.py
+++ b/logging_ext.py
@@ -118,7 +118,7 @@ def init_log(debug=False, syslog=False, logthreshold=None, logfile=None,
# setHandler method, so do it this way :$
logger.handlers = [handler]
isatty = hasattr(sys.__stdout__, 'isatty') and sys.__stdout__.isatty()
- if debug and isatty:
+ if debug and isatty and sys.platform != 'win32':
fmt = ColorFormatter(logformat, logdateformat)
def col_fact(record):
if 'XXX' in record.message:
diff --git a/test/unittest_html.py b/test/unittest_html.py
new file mode 100644
index 0000000..927425f
--- /dev/null
+++ b/test/unittest_html.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+"""unittests for logilab.common.html
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+
+__docformat__ = "restructuredtext en"
+
+from logilab.common.testlib import TestCase, unittest_main
+from logilab.common.tree import Node
+
+from logilab.common import html
+
+tree = ('root', (
+ ('child_1_1', (
+ ('child_2_1', ()), ('child_2_2', (
+ ('child_3_1', ()),
+ ('child_3_2', ()),
+ ('child_3_3', ()),
+ )))),
+ ('child_1_2', (('child_2_3', ()),))))
+
+generated_html = """\
+<table class="tree">
+<tr><td class="tree_cell" rowspan="2"><div class="tree_cell">root</div></td><td class="tree_cell_1_1">&nbsp;</td><td class="tree_cell_1_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_1_1</div></td><td class="tree_cell_1_1">&nbsp;</td><td class="tree_cell_1_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_2_1</div></td><td class="tree_cell_0_1">&nbsp;</td><td class="tree_cell_0_2">&nbsp;</td><td rowspan="2">&nbsp;</td></tr>
+<tr><td class="tree_cell_1_3">&nbsp;</td><td class="tree_cell_1_4">&nbsp;</td><td class="tree_cell_1_3">&nbsp;</td><td class="tree_cell_1_4">&nbsp;</td><td class="tree_cell_0_3">&nbsp;</td><td class="tree_cell_0_4">&nbsp;</td></tr>
+<tr><td rowspan="2">&nbsp;</td><td class="tree_cell_2_1">&nbsp;</td><td class="tree_cell_2_2">&nbsp;</td><td rowspan="2">&nbsp;</td><td class="tree_cell_4_1">&nbsp;</td><td class="tree_cell_4_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div id="selected" class="tree_cell">child_2_2</div></td><td class="tree_cell_1_1">&nbsp;</td><td class="tree_cell_1_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_3_1</div></td></tr>
+<tr><td class="tree_cell_2_3">&nbsp;</td><td class="tree_cell_2_4">&nbsp;</td><td class="tree_cell_4_3">&nbsp;</td><td class="tree_cell_4_4">&nbsp;</td><td class="tree_cell_1_3">&nbsp;</td><td class="tree_cell_1_4">&nbsp;</td></tr>
+<tr><td rowspan="2">&nbsp;</td><td class="tree_cell_2_1">&nbsp;</td><td class="tree_cell_2_2">&nbsp;</td><td rowspan="2">&nbsp;</td><td class="tree_cell_0_1">&nbsp;</td><td class="tree_cell_0_2">&nbsp;</td><td rowspan="2">&nbsp;</td><td class="tree_cell_3_1">&nbsp;</td><td class="tree_cell_3_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_3_2</div></td></tr>
+<tr><td class="tree_cell_2_3">&nbsp;</td><td class="tree_cell_2_4">&nbsp;</td><td class="tree_cell_0_3">&nbsp;</td><td class="tree_cell_0_4">&nbsp;</td><td class="tree_cell_3_3">&nbsp;</td><td class="tree_cell_3_4">&nbsp;</td></tr>
+<tr><td rowspan="2">&nbsp;</td><td class="tree_cell_2_1">&nbsp;</td><td class="tree_cell_2_2">&nbsp;</td><td rowspan="2">&nbsp;</td><td class="tree_cell_0_1">&nbsp;</td><td class="tree_cell_0_2">&nbsp;</td><td rowspan="2">&nbsp;</td><td class="tree_cell_4_1">&nbsp;</td><td class="tree_cell_4_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_3_3</div></td></tr>
+<tr><td class="tree_cell_2_3">&nbsp;</td><td class="tree_cell_2_4">&nbsp;</td><td class="tree_cell_0_3">&nbsp;</td><td class="tree_cell_0_4">&nbsp;</td><td class="tree_cell_4_3">&nbsp;</td><td class="tree_cell_4_4">&nbsp;</td></tr>
+<tr><td rowspan="2">&nbsp;</td><td class="tree_cell_4_1">&nbsp;</td><td class="tree_cell_4_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_1_2</div></td><td class="tree_cell_5_1">&nbsp;</td><td class="tree_cell_5_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_2_3</div></td><td class="tree_cell_0_1">&nbsp;</td><td class="tree_cell_0_2">&nbsp;</td><td rowspan="2">&nbsp;</td></tr>
+<tr><td class="tree_cell_4_3">&nbsp;</td><td class="tree_cell_4_4">&nbsp;</td><td class="tree_cell_5_3">&nbsp;</td><td class="tree_cell_5_4">&nbsp;</td><td class="tree_cell_0_3">&nbsp;</td><td class="tree_cell_0_4">&nbsp;</td></tr>
+</table>\
+"""
+
+def make_tree(tupletree):
+ n = Node(tupletree[0])
+ for child in tupletree[1]:
+ n.append(make_tree(child))
+ return n
+
+class UIlibHTMLGenerationTC(TestCase):
+ """ a basic tree node, caracterised by an id"""
+ def setUp(self):
+ """ called before each test from this class """
+ self.o = make_tree(tree)
+
+ def test_generated_html(self):
+ s = html.render_HTML_tree(self.o, selected_node="child_2_2")
+ self.assertTextEqual(s, generated_html)
+
+
+if __name__ == '__main__':
+ unittest_main()
diff --git a/test/unittest_testlib.py b/test/unittest_testlib.py
index eacb5b8..1bf5123 100644
--- a/test/unittest_testlib.py
+++ b/test/unittest_testlib.py
@@ -641,12 +641,14 @@ class DecoratorTC(TestCase):
@with_tempdir
def createfile(list):
- tempfile.mkstemp()
- tempfile.mkstemp()
+ fd1, fn1 = tempfile.mkstemp()
+ fd2, fn2 = tempfile.mkstemp()
dir = tempfile.mkdtemp()
- tempfile.mkstemp(dir=dir)
+ fd3, fn3 = tempfile.mkstemp(dir=dir)
tempfile.mkdtemp()
list.append(True)
+ for fd in (fd1, fd2, fd3):
+ os.close(fd)
self.assertFalse(witness)
createfile(witness)
@@ -672,11 +674,13 @@ class DecoratorTC(TestCase):
@with_tempdir
def createfile():
- tempfile.mkstemp()
- tempfile.mkstemp()
+ fd1, fn1 = tempfile.mkstemp()
+ fd2, fn2 = tempfile.mkstemp()
dir = tempfile.mkdtemp()
- tempfile.mkstemp(dir=dir)
+ fd3, fn3 = tempfile.mkstemp(dir=dir)
tempfile.mkdtemp()
+ for fd in (fd1, fd2, fd3):
+ os.close(fd)
raise WitnessException()
self.assertRaises(WitnessException, createfile)
diff --git a/test/unittest_xmlutils.py b/test/unittest_xmlutils.py
new file mode 100644
index 0000000..12fafae
--- /dev/null
+++ b/test/unittest_xmlutils.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+from logilab.common.testlib import TestCase, unittest_main
+from logilab.common.xmlutils import parse_pi_data
+
+
+class ProcessingInstructionDataParsingTest(TestCase):
+ def test_empty_pi(self):
+ """
+ Tests the parsing of the data of an empty processing instruction.
+ """
+ pi_data = u" \t \n "
+ data = parse_pi_data(pi_data)
+ self.assertEquals(data, {})
+
+ def test_simple_pi_with_double_quotes(self):
+ """
+ Tests the parsing of the data of a simple processing instruction using
+ double quotes for embedding the value.
+ """
+ pi_data = u""" \t att="value"\n """
+ data = parse_pi_data(pi_data)
+ self.assertEquals(data, {u"att": u"value"})
+
+ def test_simple_pi_with_simple_quotes(self):
+ """
+ Tests the parsing of the data of a simple processing instruction using
+ simple quotes for embedding the value.
+ """
+ pi_data = u""" \t att='value'\n """
+ data = parse_pi_data(pi_data)
+ self.assertEquals(data, {u"att": u"value"})
+
+ def test_complex_pi_with_different_quotes(self):
+ """
+ Tests the parsing of the data of a complex processing instruction using
+ simple quotes or double quotes for embedding the values.
+ """
+ pi_data = u""" \t att='value'\n att2="value2" att3='value3'"""
+ data = parse_pi_data(pi_data)
+ self.assertEquals(data, {u"att": u"value", u"att2": u"value2",
+ u"att3": u"value3"})
+
+ def test_pi_with_non_attribute_data(self):
+ """
+ Tests the parsing of the data of a complex processing instruction
+ containing non-attribute data.
+ """
+ pi_data = u""" \t keyword att1="value1" """
+ data = parse_pi_data(pi_data)
+ self.assertEquals(data, {u"keyword": None, u"att1": u"value1"})
+
+
+# definitions for automatic unit testing
+
+if __name__ == '__main__':
+ unittest_main()
+
diff --git a/textutils.py b/textutils.py
index c25d8c8..1c24c70 100644
--- a/textutils.py
+++ b/textutils.py
@@ -30,6 +30,7 @@ unquote, colorize_ansi
"""
__docformat__ = "restructuredtext en"
+import sys
import re
from unicodedata import normalize as _uninormalize
try:
@@ -434,3 +435,20 @@ def colorize_ansi(msg, color=None, style=None):
return '%s%s%s' % (escape_code, msg, ANSI_RESET)
return msg
+DIFF_STYLE = {'separator': 'cyan', 'remove': 'red', 'add': 'green'}
+
+def diff_colorize_ansi(lines, out=sys.stdout, style=DIFF_STYLE):
+ for line in lines:
+ if line[:4] in ('--- ', '+++ '):
+ out.write(colorize_ansi(line, style['separator']))
+ elif line[0] == '-':
+ out.write(colorize_ansi(line, style['remove']))
+ elif line[0] == '+':
+ out.write(colorize_ansi(line, style['add']))
+ elif line[:4] == '--- ':
+ out.write(colorize_ansi(line, style['separator']))
+ elif line[:4] == '+++ ':
+ out.write(colorize_ansi(line, style['separator']))
+ else:
+ out.write(line)
+
diff --git a/xmlutils.py b/xmlutils.py
new file mode 100644
index 0000000..5a27f0f
--- /dev/null
+++ b/xmlutils.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+"""XML utilities.
+
+This module contains useful functions for parsing and using XML data. For the
+moment, there is only one function that can parse the data inside a processing
+instruction and return a Python dictionnary.
+
+:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: General Public License version 2 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+import re
+
+RE_DOUBLE_QUOTE = re.compile('([\w\-\.]+)="([^"]+)"')
+RE_SIMPLE_QUOTE = re.compile("([\w\-\.]+)='([^']+)'")
+
+def parse_pi_data(pi_data):
+ """
+ Utilitary function that parses the data contained in an XML
+ processing instruction and returns a dictionnary of keywords and their
+ associated values (most of the time, the processing instructions contain
+ data like ``keyword="value"``, if a keyword is not associated to a value,
+ for example ``keyword``, it will be associated to ``None``).
+
+ :param pi_data: data contained in an XML processing instruction.
+ :type pi_data: unicode
+
+ :returns: Dictionnary of the keywords (Unicode strings) associated to
+ their values (Unicode strings) as they were defined in the
+ data.
+ :rtype: dict
+ """
+ results = {}
+ for elt in pi_data.split():
+ if RE_DOUBLE_QUOTE.match(elt):
+ kwd, val = RE_DOUBLE_QUOTE.match(elt).groups()
+ elif RE_SIMPLE_QUOTE.match(elt):
+ kwd, val = RE_SIMPLE_QUOTE.match(elt).groups()
+ else:
+ kwd, val = elt, None
+ results[kwd] = val
+ return results