xmlfile.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. from __future__ import absolute_import
  2. # Copyright (c) 2010-2015 openpyxl
  3. """Implements the lxml.etree.xmlfile API using the standard library xml.etree"""
  4. from contextlib import contextmanager
  5. from xml.etree.ElementTree import Element, tostring
  6. class LxmlSyntaxError(Exception):
  7. pass
  8. class _FakeIncrementalFileWriter(object):
  9. """Replacement for _IncrementalFileWriter of lxml.
  10. Uses ElementTree to build xml in memory."""
  11. def __init__(self, output_file):
  12. self._element_stack = []
  13. self._top_element = None
  14. self._file = output_file
  15. self._have_root = False
  16. @contextmanager
  17. def element(self, tag, attrib=None, nsmap=None, **_extra):
  18. """Create a new xml element using a context manager.
  19. The elements are written when the top level context is left.
  20. This is for code compatibility only as it is quite slow.
  21. """
  22. # __enter__ part
  23. self._have_root = True
  24. if attrib is None:
  25. attrib = {}
  26. self._top_element = Element(tag, attrib=attrib, **_extra)
  27. self._top_element.text = ''
  28. self._top_element.tail = ''
  29. self._element_stack.append(self._top_element)
  30. yield
  31. # __exit__ part
  32. el = self._element_stack.pop()
  33. if self._element_stack:
  34. parent = self._element_stack[-1]
  35. parent.append(self._top_element)
  36. self._top_element = parent
  37. else:
  38. self._write_element(el)
  39. self._top_element = None
  40. def write(self, arg):
  41. """Write a string or subelement."""
  42. if isinstance(arg, str):
  43. # it is not allowed to write a string outside of an element
  44. if self._top_element is None:
  45. raise LxmlSyntaxError()
  46. if len(self._top_element) == 0:
  47. # element has no children: add string to text
  48. self._top_element.text += arg
  49. else:
  50. # element has children: add string to tail of last child
  51. self._top_element[-1].tail += arg
  52. else:
  53. if self._top_element is not None:
  54. self._top_element.append(arg)
  55. elif not self._have_root:
  56. self._write_element(arg)
  57. else:
  58. raise LxmlSyntaxError()
  59. def _write_element(self, element):
  60. xml = tostring(element)
  61. self._file.write(xml)
  62. def __enter__(self):
  63. pass
  64. def __exit__(self, type, value, traceback):
  65. # without root the xml document is incomplete
  66. if not self._have_root:
  67. raise LxmlSyntaxError()
  68. class xmlfile(object):
  69. """Context manager that can replace lxml.etree.xmlfile."""
  70. def __init__(self, output_file, buffered=False, encoding=None, close=False):
  71. if isinstance(output_file, str):
  72. self._file = open(output_file, 'wb')
  73. self._close = True
  74. else:
  75. self._file = output_file
  76. self._close = close
  77. def __enter__(self):
  78. return _FakeIncrementalFileWriter(self._file)
  79. def __exit__(self, type, value, traceback):
  80. if self._close == True:
  81. self._file.close()