22
22
# importing this module is fairly slow because it has to load several
25
from bzrlib.serializer import Serializer
26
from bzrlib.trace import mutter, warning
26
from util.cElementTree import ElementTree, SubElement, Element
30
# it's in this package in python2.5
31
from xml.etree.cElementTree import (ElementTree, SubElement, Element,
32
XMLTreeBuilder, fromstring, tostring)
33
import xml.etree as elementtree
35
from cElementTree import (ElementTree, SubElement, Element,
36
XMLTreeBuilder, fromstring, tostring)
37
import elementtree.ElementTree
38
ParseError = SyntaxError
27
39
except ImportError:
28
from util.elementtree.ElementTree import ElementTree, SubElement, Element
30
from bzrlib.inventory import ROOT_ID, Inventory, InventoryEntry
31
from bzrlib.revision import Revision, RevisionReference
32
from bzrlib.errors import BzrError
35
class Serializer(object):
36
"""Abstract object serialize/deserialize"""
37
def write_inventory(self, inv, f):
38
"""Write inventory to a file"""
39
elt = self._pack_inventory(inv)
40
self._write_element(elt, f)
42
def read_inventory(self, f):
43
return self._unpack_inventory(self._read_element(f))
40
mutter('WARNING: using slower ElementTree; consider installing cElementTree'
41
" and make sure it's on your PYTHONPATH")
42
# this copy is shipped with bzr
43
from util.elementtree.ElementTree import (ElementTree, SubElement,
44
Element, XMLTreeBuilder,
46
import util.elementtree as elementtree
47
from xml.parsers.expat import ExpatError as ParseError
49
from bzrlib import errors
52
class XMLSerializer(Serializer):
53
"""Abstract XML object serialize/deserialize"""
55
squashes_xml_invalid_characters = True
57
def read_inventory_from_string(self, xml_string, revision_id=None,
59
"""Read xml_string into an inventory object.
61
:param xml_string: The xml to read.
62
:param revision_id: If not-None, the expected revision id of the
63
inventory. Some serialisers use this to set the results' root
64
revision. This should be supplied for deserialising all
65
from-repository inventories so that xml5 inventories that were
66
serialised without a revision identifier can be given the right
67
revision id (but not for working tree inventories where users can
68
edit the data without triggering checksum errors or anything).
69
:param entry_cache: An optional cache of InventoryEntry objects. If
70
supplied we will look up entries via (file_id, revision_id) which
71
should map to a valid InventoryEntry (File/Directory/etc) object.
74
return self._unpack_inventory(fromstring(xml_string), revision_id,
75
entry_cache=entry_cache)
77
raise errors.UnexpectedInventoryFormat(e)
79
def read_inventory(self, f, revision_id=None):
81
return self._unpack_inventory(self._read_element(f),
84
raise errors.UnexpectedInventoryFormat(e)
45
86
def write_revision(self, rev, f):
46
87
self._write_element(self._pack_revision(rev), f)
89
def write_revision_to_string(self, rev):
90
return tostring(self._pack_revision(rev)) + '\n'
48
92
def read_revision(self, f):
49
93
return self._unpack_revision(self._read_element(f))
95
def read_revision_from_string(self, xml_string):
96
return self._unpack_revision(fromstring(xml_string))
51
98
def _write_element(self, elt, f):
52
99
ElementTree(elt).write(f, 'utf-8')
56
103
return ElementTree().parse(f)
60
class _Serializer_v4(Serializer):
61
"""Version 0.0.4 serializer
63
You should use the serialzer_v4 singleton."""
67
def _pack_inventory(self, inv):
68
"""Convert to XML Element"""
69
e = Element('inventory')
71
if inv.root.file_id not in (None, ROOT_ID):
72
e.set('file_id', inv.root.file_id)
73
for path, ie in inv.iter_entries():
74
e.append(self._pack_entry(ie))
78
def _pack_entry(self, ie):
79
"""Convert InventoryEntry to XML element"""
81
e.set('name', ie.name)
82
e.set('file_id', ie.file_id)
83
e.set('kind', ie.kind)
85
if ie.text_size != None:
86
e.set('text_size', '%d' % ie.text_size)
88
for f in ['text_id', 'text_sha1']:
93
# to be conservative, we don't externalize the root pointers
94
# for now, leaving them as null in the xml form. in a future
95
# version it will be implied by nested elements.
96
if ie.parent_id != ROOT_ID:
97
assert isinstance(ie.parent_id, basestring)
98
e.set('parent_id', ie.parent_id)
105
def _unpack_inventory(self, elt):
106
"""Construct from XML Element
108
assert elt.tag == 'inventory'
109
root_id = elt.get('file_id') or ROOT_ID
110
inv = Inventory(root_id)
112
ie = self._unpack_entry(e)
113
if ie.parent_id == ROOT_ID:
114
ie.parent_id = root_id
119
def _unpack_entry(self, elt):
120
assert elt.tag == 'entry'
122
## original format inventories don't have a parent_id for
123
## nodes in the root directory, but it's cleaner to use one
125
parent_id = elt.get('parent_id')
126
if parent_id == None:
129
ie = InventoryEntry(elt.get('file_id'),
133
ie.text_id = elt.get('text_id')
134
ie.text_sha1 = elt.get('text_sha1')
136
## mutter("read inventoryentry: %r" % (elt.attrib))
138
v = elt.get('text_size')
139
ie.text_size = v and int(v)
144
def _pack_revision(self, rev):
145
"""Revision object -> xml tree"""
146
root = Element('revision',
147
committer = rev.committer,
148
timestamp = '%.9f' % rev.timestamp,
149
revision_id = rev.revision_id,
150
inventory_id = rev.inventory_id,
151
inventory_sha1 = rev.inventory_sha1,
154
root.set('timezone', str(rev.timezone))
157
msg = SubElement(root, 'message')
158
msg.text = rev.message
162
pelts = SubElement(root, 'parents')
163
pelts.tail = pelts.text = '\n'
164
for rr in rev.parents:
165
assert isinstance(rr, RevisionReference)
166
p = SubElement(pelts, 'revision_ref')
168
assert rr.revision_id
169
p.set('revision_id', rr.revision_id)
171
p.set('revision_sha1', rr.revision_sha1)
176
def _unpack_revision(self, elt):
177
"""XML Element -> Revision object"""
179
# <changeset> is deprecated...
180
if elt.tag not in ('revision', 'changeset'):
181
raise BzrError("unexpected tag in revision file: %r" % elt)
183
rev = Revision(committer = elt.get('committer'),
184
timestamp = float(elt.get('timestamp')),
185
revision_id = elt.get('revision_id'),
186
inventory_id = elt.get('inventory_id'),
187
inventory_sha1 = elt.get('inventory_sha1')
190
precursor = elt.get('precursor')
191
precursor_sha1 = elt.get('precursor_sha1')
193
pelts = elt.find('parents')
197
assert p.tag == 'revision_ref', \
198
"bad parent node tag %r" % p.tag
199
rev_ref = RevisionReference(p.get('revision_id'),
200
p.get('revision_sha1'))
201
rev.parents.append(rev_ref)
205
prec_parent = rev.parents[0].revision_id
206
assert prec_parent == precursor
208
# revisions written prior to 0.0.5 have a single precursor
209
# give as an attribute
210
rev_ref = RevisionReference(precursor, precursor_sha1)
211
rev.parents.append(rev_ref)
213
v = elt.get('timezone')
214
rev.timezone = v and int(v)
216
rev.message = elt.findtext('message') # text of <message>
222
"""singleton instance"""
223
serializer_v4 = _Serializer_v4()
106
# performance tuning for elementree's serialiser. This should be
107
# sent upstream - RBC 20060523.
108
# the functions here are patched into elementtree at runtime.
110
escape_re = re.compile("[&'\"<>]")
113
"'":"'", # FIXME: overkill
118
def _escape_replace(match, map=escape_map):
119
return map[match.group()]
121
def _escape_attrib(text, encoding=None, replace=None):
122
# escape attribute value
126
text = elementtree.ElementTree._encode(text, encoding)
128
return elementtree.ElementTree._encode_entity(text)
130
return escape_re.sub(_escape_replace, text)
132
text = replace(text, "&", "&")
133
text = replace(text, "'", "'") # FIXME: overkill
134
text = replace(text, "\"", """)
135
text = replace(text, "<", "<")
136
text = replace(text, ">", ">")
138
except (TypeError, AttributeError):
139
elementtree.ElementTree._raise_serialization_error(text)
141
elementtree.ElementTree._escape_attrib = _escape_attrib
143
escape_cdata_re = re.compile("[&<>]")
149
def _escape_cdata_replace(match, map=escape_cdata_map):
150
return map[match.group()]
152
def _escape_cdata(text, encoding=None, replace=None):
153
# escape character data
157
text = elementtree.ElementTree._encode(text, encoding)
159
return elementtree.ElementTree._encode_entity(text)
161
return escape_cdata_re.sub(_escape_cdata_replace, text)
163
text = replace(text, "&", "&")
164
text = replace(text, "<", "<")
165
text = replace(text, ">", ">")
167
except (TypeError, AttributeError):
168
elementtree.ElementTree._raise_serialization_error(text)
170
elementtree.ElementTree._escape_cdata = _escape_cdata
173
def escape_invalid_chars(message):
174
"""Escape the XML-invalid characters in a commit message.
176
:param message: Commit message to escape
177
:return: tuple with escaped message and number of characters escaped
181
# Python strings can include characters that can't be
182
# represented in well-formed XML; escape characters that
183
# aren't listed in the XML specification
184
# (http://www.w3.org/TR/REC-xml/#NT-Char).
185
return re.subn(u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD]+',
186
lambda match: match.group(0).encode('unicode_escape'),