class LTNetwork(LTItem, UndirectedNetwork):

    def replace(self, old_node, new_node):
        """
        Replaces the old_node by the new_node (e.g. move its position).
        """
        assert(old_node in self.points)
        assert(old_node != new_node)
        self.add_point(new_node)

        for link in self.links[old_node]:
            if link != new_node:
                self.add_link(new_node, link)

        self.remove_point(old_node)

    def _remove_duplicates(self):
        """
        Removes duplicated points close to each other.
        """
        # select points to remove by proximity
        points_to_remove = set()
        for point in self:
            for point_prime in self:
                if point != point_prime and\
                        abs(point_prime.x - point.x) <= 2 and\
                        abs(point_prime.y - point.y) <= 2 and\
                        (point_prime, point) not in points_to_remove:
                    points_to_remove.add((point, point_prime))

        removed_points = set()
        for point, point_prime in points_to_remove:
            if point in removed_points:
                continue

            for link_prime in self.links[point_prime]:
                self.links[point].add(link_prime)
                self.links[link_prime].add(point)

            # track removed points
            removed_points.add(point_prime)
            self.remove_point(point_prime)

    def _align_nodes(self):
        """
        Makes the links horizontal/vertical by aligning nodes.
        """
        # make an histogram of the most common x and y values
        raw_x_values = defaultdict(int)
        raw_y_values = defaultdict(int)
        for point in self:
            raw_x_values[point.x] += 1
            raw_y_values[point.y] += 1

        # stores which values to replace (remove close values)
        replace_x = {}
        for x in raw_x_values:
            for x_prime in raw_x_values:
                if x != x_prime and abs(x - x_prime) < 5:
                    if raw_x_values[x_prime] > raw_x_values[x]:
                        replace_x[x] = x_prime
                    elif raw_x_values[x_prime] == raw_x_values[x]:
                        # in case they are the same, we must ensure
                        # we don't substitute both by each other.
                        if x > x_prime:
                            replace_x[x] = x_prime
                        else:
                            replace_x[x_prime] = x
                    else:
                        replace_x[x_prime] = x

        replace_y = {}
        for y in raw_y_values:
            for y_prime in raw_y_values:
                if y != y_prime and abs(y - y_prime) < 5:
                    if raw_y_values[y_prime] > raw_y_values[y]:
                        replace_y[y] = y_prime
                    elif raw_y_values[y_prime] == raw_y_values[y]:
                        # in case they are the same, we must ensure
                        # we don't substitute both by each other.
                        if y > y_prime:
                            replace_y[y] = y_prime
                        else:
                            replace_y[y_prime] = y
                    else:
                        replace_y[y_prime] = y

        for point in list(self)[:]:
            if point.x in replace_x:
                self.replace(point, Point((replace_x[point.x], point.y)))

        for point in list(self)[:]:
            if point.y in replace_y:
                self.replace(point, Point((point.x, replace_y[point.y])))

    def _remove_siblings(self):
        """
        Removes nodes with only two links (are not important for table creation).
        """
        for point in list(self)[:]:
            if len(self.links[point]) == 2:
                point1 = list(self.links[point])[0]
                point2 = list(self.links[point])[1]
                assert(point1.x == point.x == point2.x or
                       point1.y == point.y == point2.y)

                self.links[point1].add(point2)
                self.links[point2].add(point1)

                self.remove_point(point)

    def _get_components(self):
        """
        Returns a list of non-overlapping networks.
        """
        networks = []

        def add_point(network, point):
            """
            Recursively adds a point and respective connected component to
            network.
            """
            network.add_point(point)
            for link in self.links[point]:
                if link not in network:
                    add_point(network, link)
                network.add_link(point, link)

        for point in self.points:
            # try to find the network of this point.
            found = None
            for network in networks:
                if point in network:
                    found = network
                    break

            # if no network exists, create one for it.
            if found is None:
                networks.append(LTNetwork())
                found = networks[-1]

            # finally, add its connected component to the network.
            add_point(found, point)

            # asserts that the connected component is on that network only.
            for link in self.links[point]:
                assert(link in found)
                for network in networks:
                    if network != found:
                        assert(point not in network)
                        assert(link not in network)

        # asserts that we didn't lost any point
        assert(sum(len(network) for network in networks) ==
               len(self))

        for network in networks:
            network.close_network()
            network.add_collinear_links()

        return networks

    def close_network(self):
        """
        Adds possible missing corners and border links to close the network.
        """
        rows_borders = sorted(list(set(point.y for point in self)))
        columns_borders = sorted(list(set(point.x for point in self)))

        x0 = columns_borders[0]
        y0 = rows_borders[0]
        x1 = columns_borders[-1]
        y1 = rows_borders[-1]

        # add possible missing corners
        for corner in ((x0, y0), (x0, y1), (x1, y1), (x1, y0)):
            self.add_point(Point(corner))

        # add possible missing bottom and top borders
        for row in (rows_borders[0], rows_borders[-1]):
            points = sorted([point for point in self if point.y == row],
                            key=lambda p: p.x)
            for i, point in enumerate(points):
                if i == 0:
                    continue
                self.add_link(points[i - 1], point)

        # add possible missing left and right borders
        for column in (columns_borders[0], columns_borders[-1]):
            points = sorted([point for point in self if point.x == column],
                            key=lambda p: p.y)
            for i, point in enumerate(points):
                if i == 0:
                    continue
                self.add_link(points[i - 1], point)

    def add_collinear_links(self):
        """
        Adds links between all points that are collinear. These collinear points
        become fully connected.
        """
        rows_borders = sorted(list(set(point.y for point in self)))
        columns_borders = sorted(list(set(point.x for point in self)))

        links_to_create = set()

        for row in rows_borders:
            # all points in this row and sorted by x
            points = sorted([point for point in self if point.y == row],
                            key=lambda p: p.x)

            # a O(N^2) loop that breaks the inner
            # if there is a link-break along the way.
            for index, point in enumerate(points[:-1]):
                index_prime = index + 1  # starting on the following point

                # while there is a link between point at index_prime and
                # the previous point at `index_prime - 1`.
                while index_prime != len(points) and \
                        points[index_prime] in self.links[points[index_prime - 1]]:
                    # add the link from point to point prime and advance
                    # point prime by one.
                    links_to_create.add((point, points[index_prime]))

                    index_prime += 1

        # Exactly the same loop, but with x<>y and row<>column.
        for column in columns_borders:
            points = sorted([point for point in self
                             if point.x == column], key=lambda p: p.y)
            for index, point in enumerate(points[:-1]):
                index_prime = index + 1
                while index_prime != len(points) and \
                        points[index_prime] in self.links[points[index_prime - 1]]:
                    links_to_create.add((point, points[index_prime]))

                    index_prime += 1

        # finally, add the links
        for point, point_prime in links_to_create:
            self.add_link(point, point_prime)

    def create_components(self):
        self._align_nodes()
        self._remove_duplicates()

        self._fix_intersections()
        self._fix_missing_links()

        self._remove_siblings()

        self.assert_strait_links()
        self.assert_no_intersections()

        return self._get_components()

    def assert_strait_links(self):
        links = self.links_list()
        for link in links:
            assert(link[0].x == link[1].x or link[0].y == link[1].y)

    def assert_no_intersections(self):
        links = self.links_list()
        for link in links:
            for link_prime in links:
                if intersect(link[0], link[1], link_prime[0], link_prime[1]):
                    raise Exception(link, link_prime)

    def _fix_intersections(self):
        """
        Computes all intersections of links and creates points in the
        intersections, subdividing the links accordingly.
        """
        links = self.links_list()

        vertical_links = [link for link in links if link[0].x == link[1].x]
        horizontal_links = [link for link in links if link[0].y == link[1].y]
        assert(len(links) == len(vertical_links) + len(horizontal_links))

        v_links_to_subdivide = defaultdict(set)
        h_links_to_subdivide = defaultdict(set)
        for link in vertical_links:
            for link_prime in horizontal_links:
                if intersect(link[0], link[1], link_prime[0], link_prime[1]):
                    point = Point((link[0].x, link_prime[0].y))
                    v_links_to_subdivide[link].add(point)
                    h_links_to_subdivide[link_prime].add(point)

        for link in h_links_to_subdivide:
            points = sorted(h_links_to_subdivide[link], key=lambda p: p.x)
            self._subdivide(link[0], link[1], points)
        for link in v_links_to_subdivide:
            points = sorted(v_links_to_subdivide[link], key=lambda p: p.y)
            self._subdivide(link[0], link[1], points)

    def _fix_missing_links(self):
        """
        Computes all points that are crossed by a line without a link and
        subdivides such line to link those points.
        """
        previous_number_of_points = len(self)

        links = self.links_list()

        vertical_links = [link for link in links if link[0].x == link[1].x]
        horizontal_links = [link for link in links if link[0].y == link[1].y]

        found_points = dict()
        for link in horizontal_links:
            points = [p for p in self if p.y == link[0].y and
                      link[0].x < p.x < link[1].x]
            if points:
                found_points[link] = points

        for link in found_points:
            points = sorted(found_points[link], key=lambda p: p.x)
            self._subdivide(link[0], link[1], points)

        found_points = dict()
        for link in vertical_links:
            points = [p for p in self if p.x == link[0].x and
                                  link[0].y < p.y < link[1].y]
            if points:
                found_points[link] = points

        for link in found_points:
            points = sorted(found_points[link], key=lambda p: p.y)
            self._subdivide(link[0], link[1], points)

        # asserts that we didn't added any new point
        assert(previous_number_of_points == len(self))

    def _subdivide(self, first_point, last_point, points):
        """
        Subdivides the link first_point<->last_point into links

            first_point<->points[0]<->...<->points[-1]<->last_point.

        Assumes `points` are sorted.
        """
        assert(len(points) > 0)
        self.remove_link(first_point, last_point)

        for point in points:
            self.add_point(point)

        if len(points) == 1:
            self.add_link(first_point, points[0])
            self.add_link(points[0], last_point)
            return

        for i, point in enumerate(points):
            if i == 0:
                point_before = first_point
                point_after = points[i + 1]
            elif i == len(points) - 1:
                point_before = points[i - 1]
                point_after = last_point
            else:
                point_before = points[i - 1]
                point_after = points[i + 1]
            self.add_link(point_before, point)
            self.add_link(point, point_after) class LTPageLayout(object):

    MIDDLE_X1 = (292 + 306.0)/2

    def __init__(self):
        self.header = LTTextHeader()
        self.center = []
        self.left = []
        self.right = []

        self._previous_column = None

    def get_center_column(self, item):
        columns = sorted(self.center, key=lambda c: c.y0, reverse=True)
        left_columns = sorted(self.left, key=lambda c: c.y0, reverse=True)

        def new_column():
            c = LTTextColumn()
            self.center.append(c)
            return c

        if not columns:
            return new_column()

        if item.y0 > columns[0].y0:
            if left_columns and item.y0 > left_columns[0].y0 > columns[0].y0:
                # is above first but there is column in the middle
                return new_column()
            else:
                return columns[0]
        elif item.y0 < columns[-1].y0:
            if left_columns and item.y0 < left_columns[-1].y0 < columns[-1].y0:
                # is below last but there is column in the middle
                return new_column()
            else:
                return columns[-1]

        # below means that item is in the middle of columns.

        for column in columns:
            # if overlapped with a column, return that one
            if item.voverlap(column):
                return column

        for column in columns:
            # if not, get the first one, extending it to below
            if item.y0 < column.y0:
                return column

        assert(False)

    def add(self, item):
        if item.x0 < item.x1 < self.MIDDLE_X1:
            if not self.left:
                self.left.append(LTTextColumn())
            column = self.left[-1]
        elif self.MIDDLE_X1 < item.x0 < item.x1:
            if not self.right:
                self.right.append(LTTextColumn())
            column = self.right[-1]
        else:
            column = self.get_center_column(item)
        column.add(item)
        self.assert_non_overlapping()

    def add_line(self, line, is_title):
        if line.x0 < line.x1 < self.MIDDLE_X1:
            # this can either be part of a centered column or a left column.

            column = None
            # if previous column is centered column
            if self._previous_column and self._previous_column in self.center:
                if not is_title(line, LTTextColumn()):
                    column = self.center[-1]

            # standard left column
            if column is None:
                if not self.left:
                    self.left.append(LTTextColumn())
                column = self.left[-1]

        elif self.MIDDLE_X1 < line.x0 < line.x1:
            if not self.right:
                self.right.append(LTTextColumn())
            column = self.right[-1]
        else:
            column = self.get_center_column(line)

        column.add(line)
        self._previous_column = column
        self.assert_non_overlapping()

    def add_header(self, line):
        self.header.add(line)

    def analyze(self):
        for element in [self.header] + self.center + self.left + self.right:
            element.analyze(None)

    def is_empty(self):
        return not (self.center or self.left)

    def expand_left(self, x0, x1):
        for left_column in self.left:
            left_column.expand_left(x0)
            left_column.expand_right(x1)

    def expand_right(self, x0, x1):
        for right_column in self.right:
            right_column.expand_left(x0)
            right_column.expand_right(x1)

    def assert_non_overlapping(self):
        """
        Asserts that the layouts are not overlapping.
        """
        for layout in self.center:
            for layout_prime in self.left + self.right:
                if len(layout) and len(layout_prime):
                    assert(not (layout_prime.voverlap(layout) and
                                layout_prime.hoverlap(layout)))

    def ordered_columns(self):
        """
        Returns the list of columns ordered according to the reading order.
        """
        if self.is_empty():
            return []

        left = sorted(self.left, key=lambda c: c.y0, reverse=True)
        right = sorted(self.right, key=lambda c: c.y0, reverse=True)

        class LTAlignedColumns:
            """
            An auxiliar class to sort centered columns and aligned columns.
            """
            def __init__(self, left, right):
                self.left = left
                self.right = right

            @property
            def y0(self):
                return self.left.y0

        aligned_columns = []
        right_columns_paired = []
        for l_column in left:
            was_found = False
            for r_column in right:
                if l_column.voverlap(r_column):
                    aligned_columns.append(LTAlignedColumns(l_column, r_column))
                    was_found = True
                    right_columns_paired.append(r_column)
                    break
            if not was_found:
                aligned_columns.append(l_column)

        # right columns must always be paired
        assert(len(right_columns_paired) == len(right))

        # sort centered and aligned columns.
        columns = sorted(self.center + aligned_columns,
                         key=lambda c: c.y0, reverse=True)

        # recover the aligned columns
        result = []
        for c in columns:
            if isinstance(c, LTAlignedColumns):
                result.append(c.left)
                result.append(c.right)
            else:
                result.append(c)
        return result

    def print_as_rectangles(self):
        for column in self.center + self.left + self.right:
            print('{{%f,%f},{%f,%f}}' % column.bbox) class Point(object):
    """
    A point with two coordinates that can be compared and used in sets.
    """
    def __init__(self, point):
        self._x = point[0]
        self._y = point[1]

    def __hash__(self):
        return hash(tuple(sorted(self.__dict__.items())))

    @property
    def x(self):
        return self._x

    @property
    def y(self):
        return self._y

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __ne__(self, other):
        return not self == other

    def __iter__(self):
        return iter([self._x, self._y])

    def __str__(self):
        return '%d,%d' % (self._x, self._y)

    def __repr__(self):
        return '<%s (%s)>' % (self.__class__.__name__, str(self)) class Paragraph(object):

    @staticmethod
    def sanitize(text):
        # hyphens in words are getting a space from PDFminer. Remove it.
        return re.sub(ur' (\-\w+?)', ur'\1', text, flags=re.U)

    def __init__(self, text):
        assert(text[-1] != '\n')
        self._text = self.sanitize(text.strip())

    def merge(self, other_line):
        text = other_line.text
        if self.text[-1] == '-':
            self._text = self._text[:-1]
        # don't merge two lines without a space in between if no hyphen
        elif text[0] != ' ' and self._text[-1] != ' ':
            text = ' ' + text
        self._text += self.sanitize(text)

    @property
    def text(self):
        return self._text

    def as_html(self):
        return '<p>%s</p>' % self.text class Header(Paragraph):

    def as_html(self):
        return '<h1>%s</h1>' % self.text class Table(LTComponent):
    """
    A table has the following interface:

    1. receives a network and converts it to a set of cells (__init__)
    2. receives items and maps then to the correct cells (add)
    3. represents itself in HTML (as_html)
    """

    class Element():
        """
        Represents an element of an HTML table. It has a colspan and rowspan.
        """
        def __init__(self, cell):
            self.cell = cell
            self.row = None
            self.column = None
            self.colspan = 0
            self.rowspan = 0

            self._lines = []
            self._min_x = 0
            self._min_y = 0

        @property
        def lines(self):
            return self._lines

        def add(self, row, column):
            if self.row is None:
                self.row = row
                self.column = column
            else:
                if self.row == row:
                    self.colspan += 1
                if self.column == column:
                    self.rowspan += 1

        def add_line(self, item, bbox):
            """
            Adds a line to the cell assuming a bounding box bbox.
            """
            # todo: this code is similar to _parse_line. Common implementation?
            def remove_dots(text):
                return text.replace(' .', '')

            text = remove_dots(item.get_text())
            if text == '.':
                return
            line = Paragraph(text)

            if not self._lines:
                # cell is empty
                self._lines.append(line)
                self._min_x = item.x0
            else:
                middle_x_cell = middle_x(bbox)
                middle_x_line = middle_x(item.bbox)
                is_centered = eq(middle_x_cell, middle_x_line, 1)

                if is_centered:
                    if self._min_y - item.y1 < 0:
                        self._lines[-1].merge(line)
                    else:
                        self._lines.append(line)
                elif eq(self._min_x, item.x0, 1):
                    self._lines.append(line)
                else:
                    self._lines[-1].merge(line)
            self._min_y = item.y0

    class EmptyTableError(Exception):
        """
        Raised by constructor when construction fails because table has no
        cells. This means that the constructed network does not constitute a
        table and should be ignored.
        """
        pass

    def __init__(self, network):
        if len(network) <= 2:
            raise self.EmptyTableError

        # construct rows and columns borders by distinct x and y's.
        self._rows_borders = sorted(list(
            set(point.y for point in network.points)))
        self._columns_borders = sorted(list(
            set(point.x for point in network.points)))

        LTComponent.__init__(self, (self._columns_borders[0],
                                    self._rows_borders[0],
                                    self._columns_borders[-1],
                                    self._rows_borders[-1]))

        self._cells = self._create_cells(network)
        self._elements = self._build_elements(self._cells)

    @staticmethod
    def _create_cells(network):
        """
        Creates cells from the network and returns then
        as LTComponents.
        """
        squares_taken = defaultdict(set)
        cells = set()

        def city_distance(point, point_prime):
            return abs(point.x - point_prime.x) + abs(point.y - point_prime.y)

        def is_perpendicular(v1_x, v1_y, v2_x, v2_y):
            return v1_x*v2_x + v1_y*v2_y == 0

        for point in sorted(network, key=lambda p: (p.x, p.y)):
            for l1 in sorted(network.links[point],
                             key=lambda p: city_distance(p, point)):
                valid_links = [
                    link for link in network.links[point] if link != l1 and
                    is_perpendicular(link.x - point.x, link.y - point.y,
                                     l1.x - point.x, l1.y - point.y)]

                for l2 in sorted(valid_links,
                                 key=lambda p: city_distance(p, point)):
                    inter = network.links[l2].intersection(network.links[l1])
                    intersection = list(inter)

                    # remove initial point
                    intersection.remove(point)

                    if len(intersection) == 0:
                        continue

                    # sort by areas: smallest area first
                    area = lambda p: (p.x - point.x)*(p.y - point.y)
                    intersection.sort(key=area)

                    # square is formed by [point, l1, l2, last_point], in this
                    # order.
                    points = [point, l1, l2, intersection[0]]

                    # compute middle position of the square
                    middle_x = sum(point.x for point in points)/4.
                    middle_y = sum(point.y for point in points)/4.

                    # check if any point already has one of its squares
                    # (at most 4) used.
                    is_taken = False
                    square = range(4)
                    for i in range(4):
                        # compute the position of the point in relation to the
                        # middle corresponding to one of the following squares
                        # position: [(1,1), (-1,1), (1,-1), (-1,-1)]
                        vx = middle_x - points[i].x
                        vy = middle_y - points[i].y

                        square[i] = (int(vx/abs(vx)), int(vy/abs(vy)))

                        belongs = square[i] in squares_taken[points[i]]

                        is_taken = is_taken or belongs

                    if not is_taken:

                        cell = LTComponent((point.x, point.y,
                                            intersection[0].x, intersection[0].y))

                        cells.add(cell)

                        for i in range(4):
                            squares_taken[points[i]].add(square[i])
                        break

        return cells

    def _build_elements(self, cells):
        """
        Converts the cells into elements.
        """
        elements = []
        for cell in cells:
            elements.append(self.Element(cell))

        for row in reversed(self._rows_borders[:-1]):
            for column in self._columns_borders[:-1]:
                for cell_index, cell in enumerate(cells):
                    if cell.y0 < row + 0.1 < cell.y1 and\
                       cell.x0 < column + 0.1 < cell.x1:
                        elements[cell_index].add(row, column)

        return sorted(elements, key=lambda e: (e.cell.x0, e.cell.y0))

    @property
    def cells(self):
        return self._cells

    def add(self, item):
        """
        Adds a text item to the table, inserting it into the correct cell.
        """
        for element in self._elements:
            if element.cell.is_hoverlap(item) and element.cell.is_voverlap(item):
                element.add_line(item, element.cell.bbox)
                break

    def as_html(self):

        string = ''
        for row in reversed(self._rows_borders[:-1]):
            string += '<tr>\n'
            for column in self._columns_borders[:-1]:
                for element in self._elements:
                    if element.column == column and element.row == row:

                        lines = element.lines
                        colspan = element.colspan
                        rowspan = element.rowspan

                        text = '\n'.join(line.as_html() for line in lines)

                        if colspan:
                            colspan = 'colspan="%d"' % (colspan + 1)
                        else:
                            colspan = ''

                        if rowspan:
                            rowspan = 'rowspan="%d"' % (rowspan + 1)
                        else:
                            rowspan = ''

                        attributes = ''
                        if rowspan or colspan:
                            attributes = ' '
                        if rowspan and colspan:
                            attributes += rowspan + ' ' + colspan
                        else:
                            attributes += rowspan + colspan

                        string += '<td%s>%s</td>\n' % (attributes, text)
            string += '</tr>\n'
        return '<table>\n%s</table>' % string class SimpleImage(LTImage):

    def __init__(self, ltimage):
        assert(isinstance(ltimage, LTImage))
        LTComponent.__init__(self, ltimage.bbox)
        self._name = ltimage.name
        self._stream = ltimage.stream

    def as_html(self):
        return '<p>(Ver imagem no documento original.)</p>' class BlockquoteStart(object):

    def as_html(self):
        return '<blockquote>' class BlockquoteEnd(object):

    def as_html(self):
        return '</blockquote>' class Meta(object):
    def __init__(self):
        # suplementary information has a page associated to it.
        self._suplement_page = None
        self._pages = []
        self._series = 0
        self._number = 0
        self._date = datetime.date(1980, 1, 1)
        self._document_version = 0

    def set_header_info(self, series, number, date):
        if self._series == 0:
            self._series = series
            self._number = number
            self._date = date
        else:
            assert(self._series == series)
            assert(self._number == number)
            assert(self._date == date)

    def add_page(self, page):
        try:
            page = int(page)
        except ValueError:
            numbers = page.split('-')
            self._suplement_page = int(numbers[0])
            page = int(numbers[1].strip()[1:-1])

        assert(isinstance(page, int))
        assert(page not in self._pages)
        if self._pages:
            assert(page == self._pages[-1] + 1)
        self._pages.append(page)

    @staticmethod
    def _build_date(piece):
        piece = piece.split(' de ')
        month = MONTH_NAME_TO_MONTH[piece[1].lower().strip()]
        return datetime.date(int(piece[2]), month, int(piece[0]))

    def _parse_header_v1(self, header):
        """
        Parses the header of documents posterior to ~2006.
        """
        if 'BoldMT' in header[0][0].fontname:
            page_no = header[0].get_text()
            document_identifier = header[1].get_text().strip()
        else:
            page_no = header[1].get_text()
            document_identifier = header[0].get_text().strip()

        self.add_page(page_no)

        document_identifier = document_identifier.split(',')[1]
        pieces = document_identifier.split(u'—')

        # extract header info
        pieces[0] = int(pieces[0][:2])
        pieces[1] = int(pieces[1][5:-1])
        pieces[2] = self._build_date(pieces[2])

        self.set_header_info(series=pieces[0], number=pieces[1], date=pieces[2])

        self._document_version = 1

    def _parse_header_v2(self, header):
        """
        Parses the header of documents prior to ~2006.
        """
        if header[0][0].fontname == 'Dutch801BT-Roman':
            page_no = int(header[0].get_text())
            series_identifier = header[1].get_text()
            number_and_date = header[2].get_text()
        else:
            page_no = int(header[2].get_text())
            series_identifier = header[1].get_text()
            number_and_date = header[0].get_text()

        self.add_page(page_no)

        series = ROMAN_TO_NUMBER[series_identifier.split(u'—')[1]
                                 .lstrip().split(' ')[0]]

        pieces = number_and_date.split(u'—')
        number = int(pieces[0].strip().split(' ')[1])
        try:
            date = datetime.datetime.strptime(pieces[1].strip(), '%d-%m-%Y').date()
        except ValueError:
            # version that uses full date.
            date = self._build_date(pieces[1])

        self.set_header_info(series, number, date)

        self._document_version = 2

    def parse_header(self, header):
        if len(header) == 2:
            self._parse_header_v1(header)
        elif len(header) == 3:
            self._parse_header_v2(header)
        else:
            raise NotImplementedError('Size of header is %d' % len(header))

    @property
    def version(self):
        return self._document_version

    @property
    def pages(self):
        return self._pages

    @property
    def year(self):
        return self._date.year def eq(value1, value2, epsilon):
    return abs(value1 - value2) < epsilon def int_round(x, base=1):
    return int(base * round(float(x)/base)) def middle_x(bbox):
    return (bbox[0] + bbox[2])/2.
from pdfminer.layout import LTTextGroup, LAParams, LTLine, LTRect, LTFigure, \
    LTTextBox
from pdfminer.converter import PDFLayoutAnalyzer
from pdfminer.utils import apply_matrix_pt
from pt_law_parser.layout import LTNetwork, LTPageLayout
from pt_law_parser.point import Point
from pt_law_parser.html import Paragraph, Header, Table, SimpleImage, \
    BlockquoteStart, BlockquoteEnd
from pt_law_parser.meta import Meta
from pt_law_parser.auxiliar import eq, int_round, middle_x
        if crosses_middle(column):
            return 0

        return self._parameters.citing_space(self.meta)*self._is_citing

    def add(self, element):
        self._result_lines.append(element)

    def add_header(self, line):
        self.add(Header(line.get_text()))
        self._titles.append(line)

    def add_paragraph(self, line):
        self.add(Paragraph(line.get_text()))

    def add_table(self, table):
        if table not in self._result_lines:
            self.add(table)

    def add_image(self, image):
        if image not in self._result_lines:
            self.add(image)

    def merge(self, line):
        if isinstance(self._result_lines[-1], SimpleImage):
            # situation where the text is not a paragraph, but a continuation
            # of the text inside the image (e.g. an Equation).
            return self.add_paragraph(line)

        self._result_lines[-1].merge(Paragraph(line.get_text()))

    def _parse_line(self, line, column):
        # todo: parse italics inside sentences

        if line == self.previous_line:
            if self.is_title(line, column):
                self.add_header(line)
            else:
                self.add_paragraph(line)
        elif self.is_starting_cite(line, column):
            # a start citing is always a title (and is not always centered.)
            self.add_header(line)

        elif self.is_title(line, column):
            is_column_jump = self.previous_line not in column

            if self.is_title(self.previous_line, column) and \
                    self.previous_line.y0 - line.y1 < 0 and not \
                    is_column_jump:
                self.merge(line)
            else:
                self.add_header(line)
        else:
            # is_text = not self.is_title(line, column)
            if self.is_paragraph(line, column) or \
                    self.is_title(self.previous_line, column):
                self.add_paragraph(line)
            else:
                self.merge(line)

    @staticmethod
    def _last_page_limit(items):
        """
        Computes the y that separates irrelevant content of the last page from
        relevant one.
        """
        rectangles = [item for item in items if isinstance(item, LTRect)]
        lines = [item for item in items if isinstance(item, LTLine)]
        lines.sort(key=lambda item: item.y0)
        rectangles.sort(key=lambda item: item.y0)

        # documents from 2012-2014 have a single thicker line.
        if lines and rectangles and \
                lines[0].linewidth == 1 and lines[0].x0 == rectangles[0].x0 and\
                lines[0].x1 == rectangles[0].x1:
            return lines[0].y0

        if len(lines) >= 2 and crosses_middle(lines[1]) and \
                lines[0].x0 > MIDDLE_X1:
            return lines[1].y0
        # documents from 1997
        elif len(rectangles) > 1 and eq(rectangles[1].y1, 381.590, 2):
            return 381.590
        else:
            return 0

    def _is_summary_page(self, items):
        # todo: this is too broad. Make it more restrict.
        items = [item for item in items if isinstance(item, (LTRect, LTLine))]
        items = sorted(items, key=lambda x: (x.x0, x.y0))
        rectangles = [item for item in items if isinstance(item, LTRect)]

        if not rectangles:
            return False

        # summary of v2 documents.
        for rectangle in rectangles:
            # if is centered but not on the bottom of the page.
            if self.is_page_centered(rectangle) and not rectangle.y1 < 80:
                return True

        # summary page always contains items
        if len(items) < 1:
            return False

        if not isinstance(items[0], LTRect):
            return False

        # situation where we have a left-rectangle in summary page
        if eq(items[0].x0, 50, 2) and eq(items[0].x1, 169.512, 2):
            return True
        else:
            return False

    def _create_tables(self, items):
        self._tables = []
        _network = LTNetwork()

        # merge all LTNetworks in a single network _network.
        for item in items:

            if isinstance(item, LTNetwork):