#!/usr/bin/python3
# -*- coding:Utf-8 -*-

# PyScribus, python library for Scribus SLA
# Copyright (C) 2020-2023 Étienne Nadji
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

"""
Classes for SLA Pages / Master pages.
"""

# Scribus pages are  arranged in a vast scratch space,  where 1 unit ==
# 1/72 inch.  Positive x is to  the right, and positive  y is downward.
# The positions, width and height in  the elements are in scratch space
# coordinates  with the  exception  of POCOOR  and COCOOR  coordinates,
# which are in the rotated and translated space of a PAGEOBJECT.

# Imports ===============================================================#

import lxml
import lxml.etree as ET

import pyscribus.logs as logs
import pyscribus.exceptions as exceptions
import pyscribus.dimensions as dimensions

from pyscribus.common.xml import *

# Variables globales ====================================================#

__author__ = "Etienne Nadji <etnadji@eml.cc>"

# Classes ===============================================================#

class PageAbstract(PyScribusElement):
    """
    Abstract class for pages and master pages.

    Use Page or MasterPage classes instead.

    :type kwargs: dict
    :param kwargs: Quick setting (see kwargs table in Page, MasterPage)

    .. seealso:: :class:`MasterPage`, :class:`Page`
    """

    orientation_xml = {"portrait": "0", "landscape": "1"}
    autoguides_origin_xml = {"page": "0", "margins": "1"}

    effect_source_xml = {"internal": "0", "external": "1"}
    effect_mobile_line_xml = {"horizontal": "0", "vertical": 1}

    effect_type_xml = {
        "none": "0",
        "masks": "1",
        "box": "2",
        "dissolve": "3",
        "glitter": "4",
        "break": "5",
        "delete": "6"
    }

    # NOTE These directions are in the correct order, but Scribus only allows
    # the user to select "left-to-right" in its GUI.
    effect_direction_xml = {
        "left-to-right": "0",
        "top-to-bottom": "1",
        "bottom-to-top": "2",
        "right-to-left": "3",
        "tl-to-br": "4",
    }

    def __init__(self, **kwargs):
        PyScribusElement.__init__(self)

        self.box = dimensions.DimBox()

        self.borders = {
            "top": dimensions.Dim(40),
            "left": dimensions.Dim(40),
            "right": dimensions.Dim(40),
            "bottom": dimensions.Dim(40),
        }

        self.name = ""
        self.master_name = ""

        self.orientation = "portrait"

        self.paper_size = ""

        self.number = -1

        #--- FIXME Tester pour savoir comment constituer les listes ------

        self.guides = {
            "horizontal": [],
            "vertical": []
        }

        #--- FIXME Not documented ----------------------------------------

        # For multipage spreads, which page in the spread is the left most
        # LEFT="0"

        # PRESET="0"

        #--- Auto guides -------------------------------------------------

        # FIXME Understand AGSelection usage
        # AGSelection="0 0 0 0"

        self.auto_guides = {
            "lines": {
                "count": 0,
                "gap": dimensions.Dim(0),
                "origin": "page"
            },
            "columns": {
                "count": 0,
                "gap": dimensions.Dim(0),
                "origin": "page"
            },
            "selection": []
        }

        #--- PDF effects -------------------------------------------------

        self.effect = {
            # @pageEffectDuration
            "duration": dimensions.Dim(1, "sec"),
            # @pageViewDuration
            "view-duration": dimensions.Dim(1, "sec"),
            # @effectType
            "type": "none",
            # @Dm
            "mobile-lines": "horizontal",
            # @M
            "source": "internal",
            # @Di
            "direction": "left-to-right"
        }

        #--- FIXME Not documented ----------------------------------------

        # Same as DOCUMENT/PDF/Effekte (PDF display effects) attributes ?

        #-----------------------------------------------------------------

        if kwargs:
            self._quick_setup(kwargs)

    def _quick_setup(self, settings):
        """
        Method for defining (master)page settings from class
        instanciation kwargs.

        :type settings: dict
        :param settings: Kwargs dictionnary
        """

        if settings:
            PyScribusElement._quick_setup(self, settings)

            for setting_name, setting_value in settings.items():

                # (Master) page box -------------------------------------------

                if setting_name == "posx":
                    # self.box.coords["top-left"][0].value = float(setting_value)
                    self.box.setx("top-left", float(setting_value))

                if setting_name == "posy":
                    # self.box.coords["top-left"][1].value = float(setting_value)
                    self.box.sety("top-left", float(setting_value))

                if setting_name == "width":
                    self.box.dims["width"].value = float(setting_value)

                if setting_name == "height":
                    self.box.dims["height"].value = float(setting_value)

                # Borders -----------------------------------------------------

                if setting_name in [
                        "rightborder", "leftborder",
                        "topborder", "bottomborder"]:
                    side = setting_name.split("border")[0]
                    self.borders[side].value = float(setting_value)

                if setting_name == "borders":
                    # NOTE We solve borders settings like CSS margin property
                    # If only one value : same border for all sides
                    # If two values: vertical and horizontal borders
                    # If three values: top horizontal bottom
                    # If four values: top right bottom left

                    if isinstance(setting_value, list):
                        setting_len = len(setting_value)

                        if setting_len == 1:

                            for side in ["top", "right", "bottom", "left"]:
                                self.borders[side].value = float(
                                    setting_value[0]
                                )

                        if setting_len == 2:
                            sides = zip(
                                [["top", "bottom"], ["right", "left"]],
                                setting_value
                            )

                            for side in sides:
                                for s in side[0]:
                                    self.borders[s].value = float(side[1])

                        if setting_len == 3:
                            self.borders["top"].value = float(setting_value[0])

                            for side in ["right", "left"]:
                                self.borders[side].value = float(
                                    setting_value[1]
                                )

                            self.borders["bottom"].value = float(
                                setting_value[2]
                            )

                        if setting_len == 4:
                            sides = zip(
                                ["top", "right", "bottom", "left"],
                                setting_value
                            )

                            for side in sides:
                                self.borders[side[0]].value = float(side[1])

                # -------------------------------------------------------------

    def set_orientation(self, orientation):
        """
        Set (master) page orientation.

        :param orientation: "portrait" or "landscape"
        :type orientation: string
        :returns: boolean
        :rtype: boolean
        """

        if orientation.lower() in PageAbstract.orientation_xml.keys():
            self.orientation = orientation.lower()
            # FIXME TODO Modify (master) page box accordingly

            return True

        else:
            raise ValueError(
                "orientation parameter of set_orientation must be "\
                "portrait' or 'landscape'"
            )

    def fromxml(self, xml, master=False):
        """
        Set (master) page attributes according to LXML Element

        :param xml: (Master) page source as XML element
        :type xml: lxml._Element
        :param master: If the page is a master page or not
        :type master: boolean
        :returns: boolean
        :rtype: boolean
        """
        if master:
            tag = "MASTERPAGE"
        else:
            tag = "PAGE"

        if xml.tag == tag:
            #--- Box settings -------------------------------------

            posx = xml.get("PAGEXPOS")
            posy = xml.get("PAGEYPOS")
            dim_width = xml.get("PAGEWIDTH")
            dim_height = xml.get("PAGEHEIGHT")

            valid_box = 0

            for test in [posx, posy, dim_width, dim_height]:
                if test is not None:
                    valid_box += 1

            if valid_box == 4:

                self.box.set_box(
                    top_lx=posx, top_ly=posy,
                    width=dim_width, height=dim_height
                )

            #--- Name / Master page name --------------------------

            self.name = xml.get("NAM")
            self.master_name = xml.get("MNAM")

            #--- Page number --------------------------------------

            page_number = xml.get("NUM")

            if page_number is not None:
                # NOTE Page 0 is the first page so we adjust to a human
                # readable count
                self.number = int(page_number) + 1

            #--- Paper size name and orientation ------------------

            paper_size = xml.get("Size")
            if paper_size is not None:
                self.paper_size = paper_size

            orientation = xml.get("Orientation")
            if orientation is not None:

                for h,x in PageAbstract.orientation_xml.items():
                    if orientation == x:
                        self.orientation = h
                        break

            #--- Borders ------------------------------------------

            for b in ["left", "top", "bottom", "right"]:
                att = "BORDER{}".format(b.upper())
                self.borders[b] = dimensions.Dim(float(xml.get(att)))

            #--- Guides -------------------------------------------

            for guide_type in ["vertical", "horizontal"]:

                att_name = "{}Guides".format(guide_type.capitalize())

                att = xml.get(att_name)
                if att is not None:
                    if att:
                        # NOTE Example of att value : "42.5197 56.6929 "

                        guides_list = []
                        guides_values = [g.strip() for g in att.split(" ")]

                        for g in guides_values:

                            if g:

                                guides_list.append(
                                    dimensions.Dim(float(g))
                                )

                        # self.guides[guide_type] = att
                        self.guides[guide_type] = guides_list

            #--- Auto guides --------------------------------------

            for case in [["lines", "horizontal"], ["columns", "vertical"]]:
                ag_count = xml.get("AG{}AutoCount".format(case[1]))
                ag_gap = xml.get("AG{}AutoGap".format(case[1]))
                ag_origin = xml.get("AG{}AutoRefer".format(case[1]))

                if ag_count is not None:
                    ag_count = int(ag_count)
                    self.auto_guides[case[0]]["count"] = ag_count

                if ag_gap is not None:
                    ag_gap = float(ag_gap)
                    self.auto_guides[case[0]]["gap"].value = ag_gap

                if ag_origin is not None:

                    for human,code in PageAbstract.autoguides_origin_xml.items():
                        if ag_origin == code:
                            self.auto_guides[case[0]]["origin"] = human

            #--- PDF effects -------------------------------------------------

            duration = xml.get("pageEffectDuration")
            if duration is not None:
                self.effect["duration"].value = int(duration)

            view_duration = xml.get("pageViewDuration")
            if view_duration is not None:
                self.effect["view-duration"].value = int(view_duration)

            effect_type = xml.get("effectType")
            if effect_type is not None:
                for human, code in PageAbstract.effect_type_xml.items():
                    if effect_type == code:
                        self.effect["type"] = human
                        break

            effect_lines = xml.get("Dm")
            if effect_lines is not None:
                for human, code in PageAbstract.effect_mobile_line_xml.items():
                    if effect_lines == code:
                        self.effect["mobile-lines"] = human
                        break

            effect_source = xml.get("M")
            if effect_source is not None:
                for human, code in PageAbstract.effect_source_xml.items():
                    if effect_source == code:
                        self.effect["source"] = human
                        break

            effect_direction = xml.get("Di")
            if effect_direction is not None:
                for human, code in PageAbstract.effect_direction_xml.items():
                    if effect_direction == code:
                        self.effect["direction"] = human
                        break

            #--- FIXME This records undocumented attributes -------

            self.undocumented = all_undocumented_to_python(xml)
            # self.undocumented = undocumented_to_python(
                # xml,
                # [
                    # "LEFT", "PRESET",
                    # "AGSelection",
                    # "pageEffectDuration", "pageViewDuration", "effectType", # "Dm", "M", "Di"
                # ]
            # )

            return True

        return False

    def toxml(self, master=False):
        """
        Returns (master) page as LXML Element

        :param master: If the page is a master page or not
        :type master: bool
        :returns: xml
        :rtype: lxml._Element
        """

        #--- Appropriate tag if master page or page -----------

        if master:
            tag = "MASTERPAGE"
        else:
            tag = "PAGE"

        xml = ET.Element(tag)

        #--- Size and dimensions ------------------------------

        xml.attrib["PAGEXPOS"] = self.box.coords["top-left"][0].toxmlstr()
        xml.attrib["PAGEYPOS"] = self.box.coords["top-left"][1].toxmlstr()

        xml.attrib["PAGEWIDTH"] = self.box.dims["width"].toxmlstr()
        xml.attrib["PAGEHEIGHT"] = self.box.dims["height"].toxmlstr()

        #--- Borders ------------------------------------------

        for b in ["left", "right", "top", "bottom"]:
            att = "BORDER{}".format(b.upper())
            xml.attrib[att] = self.borders[b].toxmlstr(True)

         #--- Page number --------------------------------------

        if self.number > 0:
            # NOTE Page 0 is the first page so we adjust back
            # from human counting to computer counting
            xml.attrib["NUM"] = str(self.number - 1)

        #--- Page name and master page name -------------------

        xml.attrib["NAM"] = self.name
        xml.attrib["MNAM"] = self.master_name

        #--- (Master) page size name and orientation ----------

        xml.attrib["Size"] = self.paper_size
        xml.attrib["Orientation"] = PageAbstract.orientation_xml[self.orientation]

        #--- Guides -------------------------------------------

        for guide_type,guides in self.guides.items():
            att_name = "{}Guides".format(guide_type.capitalize())

            # NOTE Example of att value : "42.5197 56.6929 "

            guides_str = " ".join([g.toxmlstr(True) for g in guides])

            if guides_str.strip():
                xml.attrib[att_name] = guides_str
            else:
                xml.attrib[att_name] = ""

        #--- Auto guides --------------------------------------

        for case in [["lines", "horizontal"], ["columns", "vertical"]]:
            # Page lines / columns count
            # AGhorizontalAutoCount="2"
            # AGverticalAutoCount="3"
            count_att = "AG{}AutoCount".format(case[1])

            # Page lines / columns gap (gouttière)
            # AGhorizontalAutoGap="17.007874015748"
            # AGverticalAutoGap="22.6771653543307"
            gap_att = "AG{}AutoGap".format(case[1])

            # Page lines / columns origin
            # 0 = Page 1 = Margins
            # AGhorizontalAutoRefer="0"
            # AGverticalAutoRefer="0"
            orig_att = "AG{}AutoRefer".format(case[1])

            xml.attrib[count_att] = str(self.auto_guides[case[0]]["count"])
            xml.attrib[gap_att] = self.auto_guides[case[0]]["gap"].toxmlstr()
            xml.attrib[orig_att] = PageAbstract.autoguides_origin_xml[self.auto_guides[case[0]]["origin"]]

        #--- PDF effects -------------------------------------------------

        xml.attrib["pageEffectDuration"] = self.effect["duration"].toxmlstr()
        xml.attrib["pageViewDuration"] = self.effect["view-duration"].toxmlstr()
        xml.attrib["effectType"] = PageAbstract.effect_type_xml[self.effect["type"]]
        xml.attrib["Dm"] = PageAbstract.effect_mobile_line_xml[self.effect["mobile-lines"]]
        xml.attrib["M"] = PageAbstract.effect_source_xml[self.effect["source"]]
        xml.attrib["Di"] = PageAbstract.effect_direction_xml[self.effect["direction"]]

        #--- FIXME This exports undocumented attributes -------

        try:
            # xml = undocumented_to_xml(xml, self.undocumented)
            xml, undoc_attribs = all_undocumented_to_xml(
                xml, self.undocumented, True, tag,
                logger=logs.getLogger()
            )

        except AttributeError:
            # NOTE If fromxml was not used
            pass

        return xml

    def fromdefault(self, master=False, default=False):
        """
        Set (master) page attributes according to known defaults.

        :param master: If the page is a master page or not
        :param default: Name of the set of defaults ("a4", "letter")
        :type master: bool
        :type default: str
        """

        for border in self.borders.values():
            border.value = 40

        dim_default = "a4"

        if default:
            for format_name in ["a4", "letter"]:
                if default.lower() == format_name:
                    dim_default = format_name
                    break

        self.master_name = "Normal"

        self.box.dims["width"].fromdefault("{}-width".format(dim_default))
        self.box.dims["height"].fromdefault("{}-height".format(dim_default))

        self.box.set_box(
            top_lx=100.0, top_ly=20.0,
            width=self.box.dims["width"].value,
            height=self.box.dims["height"].value
        )


class Page(PageAbstract):
    """
    Page in SLA

    To add objects to this page, use Document.append()

    :type kwargs: dict
    :param kwargs: Quick setting (see kwargs table)

    +--------------+---------------------------------+------------+
    | Kwargs       | Setting                         | Value type |
    +==============+=================================+============+
    | default      | Equivalent to a ``fromdefault`` | boolean    |
    |              | call.                           | or string  |
    |              |                                 |            |
    |              | Value being True or the name of |            |
    |              | the default set.                |            |
    +--------------+---------------------------------+------------+
    | posx         | X position of the page          | float      |
    +--------------+---------------------------------+------------+
    | posy         | Y position of the page          | float      |
    +--------------+---------------------------------+------------+
    | width        | Page width                      | float      |
    +--------------+---------------------------------+------------+
    | height       | Page height                     | float      |
    +--------------+---------------------------------+------------+
    | borders      | Shorthand for :                 | List of    |
    |              |                                 | floats     |
    |              | *rightborder*, *leftborder*,    |            |
    |              | *topborder*, *bottomborder*     |            |
    |              |                                 |            |
    |              | Read like the CSS margin        |            |
    |              | property:                       |            |
    |              |                                 |            |
    |              | **With 1 float in the list :**  |            |
    |              |                                 |            |
    |              | [top & right & bottom & left]   |            |
    |              |                                 |            |
    |              | **With 2 float in the list :**  |            |
    |              |                                 |            |
    |              | [top & bottom, right & left]    |            |
    |              |                                 |            |
    |              | **With 3 float in the list :**  |            |
    |              |                                 |            |
    |              | [top, right & left, bottom]     |            |
    |              |                                 |            |
    |              | **With 4 float in the list :**  |            |
    |              |                                 |            |
    |              | [top, right, bottom, left]      |            |
    +--------------+---------------------------------+------------+
    | rightborder  | Right border                    | float      |
    +--------------+---------------------------------+------------+
    | leftborder   | Left border                     | float      |
    +--------------+---------------------------------+------------+
    | topborder    | Top border                      | float      |
    +--------------+---------------------------------+------------+
    | bottomborder | Bottom border                   | float      |
    +--------------+---------------------------------+------------+
    """

    def __init__(self, **kwargs):
        PageAbstract.__init__(self)
        PageAbstract._quick_setup(self, kwargs)

    def toxml(self):
        """
        Returns page as LXML Element

        :returns: xml
        :rtype: lxml._Element
        """
        return PageAbstract.toxml(self, False)

    def fromxml(self, xml):
        """
        Set page attributes according to LXML Element

        :param xml: Page source as XML element
        :type xml: lxml._Element
        :returns: bool
        :rtype: bool
        """
        return PageAbstract.fromxml(self, xml, False)

    def fromdefault(self, default=False):
        """
        Set page attributes according to known defaults.

        :param default: Name of the set of defaults ("a4", "letter")
        :type default: str
        """
        PageAbstract.fromdefault(self, False, default)

    def set_orientation(self, orientation):
        """
        Set page orientation.

        :param orientation: "portrait" or "landscape"
        :type orientation: str
        :returns: bool
        :rtype: bool
        """
        return PageAbstract.set_orientation(self, orientation)


class MasterPage(PageAbstract):
    """
    Master page in SLA

    To add objects to this master page, use Document.append()

    :type kwargs: dict
    :param kwargs: Quick setting (see kwargs table)

    +--------------+---------------------------------+------------+
    | Kwargs       | Setting                         | Value type |
    +==============+=================================+============+
    | default      | Equivalent to a ``fromdefault`` | boolean    |
    |              | call.                           | or string  |
    |              |                                 |            |
    |              | Value being True or the name of |            |
    |              | the default set.                |            |
    +--------------+---------------------------------+------------+
    | posx         | X position of the page          | float      |
    +--------------+---------------------------------+------------+
    | posy         | Y position of the page          | float      |
    +--------------+---------------------------------+------------+
    | width        | Page width                      | float      |
    +--------------+---------------------------------+------------+
    | height       | Page height                     | float      |
    +--------------+---------------------------------+------------+
    | borders      | Shorthand for :                 | List of    |
    |              |                                 | floats     |
    |              | *rightborder*, *leftborder*,    |            |
    |              | *topborder*, *bottomborder*     |            |
    |              |                                 |            |
    |              | Read like the CSS margin        |            |
    |              | property:                       |            |
    |              |                                 |            |
    |              | **With 1 float in the list :**  |            |
    |              |                                 |            |
    |              | [top & right & bottom & left]   |            |
    |              |                                 |            |
    |              | **With 2 float in the list :**  |            |
    |              |                                 |            |
    |              | [top & bottom, right & left]    |            |
    |              |                                 |            |
    |              | **With 3 float in the list :**  |            |
    |              |                                 |            |
    |              | [top, right & left, bottom]     |            |
    |              |                                 |            |
    |              | **With 4 float in the list :**  |            |
    |              |                                 |            |
    |              | [top, right, bottom, left]      |            |
    +--------------+---------------------------------+------------+
    | rightborder  | Right border                    | float      |
    +--------------+---------------------------------+------------+
    | leftborder   | Left border                     | float      |
    +--------------+---------------------------------+------------+
    | topborder    | Top border                      | float      |
    +--------------+---------------------------------+------------+
    | bottomborder | Bottom border                   | float      |
    +--------------+---------------------------------+------------+
    """

    def __init__(self, **kwargs):
        PageAbstract.__init__(self)
        PageAbstract._quick_setup(self, kwargs)

    def toxml(self):
        """
        Returns master page object as LXML Element

        :returns: xml
        :rtype: lxml._Element
        """
        return PageAbstract.toxml(self, True)

    def fromxml(self, xml):
        """
        Set master page attributes according to LXML Element

        :param xml: Master page source as XML element
        :type xml: lxml._Element
        :returns: bool
        :rtype: bool
        """
        return PageAbstract.fromxml(self, xml, True)

    def fromdefault(self, default=False):
        """
        Set master page attributes according to known defaults.

        :param default: Name of the set of defaults ("a4", "letter")
        :type default: str
        """
        PageAbstract.fromdefault(self, True, default)

    def set_orientation(self, orientation):
        """
        Set master page orientation.

        :param orientation: "portrait" or "landscape"
        :type orientation: str
        :returns: bool
        :rtype: bool
        """
        return PageAbstract.set_orientation(self, orientation)


class PageSet(PyScribusElement):
    """
    Page set object.

    DOCUMENT/PageSets/Set
    """

    DEFAULTS = {
        "Single Page": {"first": 0, "rows": 1, "columns": 1, "names": []},
        "Facing Pages": {
            "first": 1, "rows": 1, "columns": 2,
            "names": ["Left Page", "Right Page"]
        },
        "3-Fold": {
            "first": 0, "rows": 1, "columns": 3,
            "names": ["Left Page", "Middle", "Right Page"]
        },
        "4-Fold": {
            "first": 0, "rows": 1, "columns": 4,
            "names": [
                "Left Page", "Middle Left", "Middle Right", "Right Page"
            ]
        }
    }

    def __init__(self):
        PyScribusElement.__init__(self)

        self.pyscribus_defaults = [k for k in PageSet.DEFAULTS.keys()]

        self.name = ""
        self.first_page = 0
        self.rows = 0
        self.columns = 0
        self.pages = []

    def fromdefault(self, default):
        """
        """

        if default in self.pyscribus_defaults:

            accurate = PageSet.DEFAULTS[default]

            self.name = default
            self.rows = accurate["rows"]
            self.pages = accurate["names"]
            self.columns = accurate["columns"]
            self.first_page = accurate["first"]

            return True
        else:
            return False

    def toxml(self):
        """
        :returns: xml
        :rtype: lxml._Element
        """

        xml = ET.Element("Set")

        #------------------------------------------------------

        xml.attrib["Name"] = self.name
        xml.attrib["FirstPage"] = str(self.first_page)
        xml.attrib["Rows"] = str(self.rows)
        xml.attrib["Columns"] = str(self.columns)

        #--- Page names ---------------------------------------

        if self.pages:

            for n in self.pages:

                pnx = ET.Element("PageNames")
                pnx.attrib["Name"] = n
                xml.append(pnx)

        #--- FIXME This exports undocumented attributes -------

        try:
            xml = undocumented_to_xml(xml, self.undocumented, no_none=True)
        except AttributeError:
            # NOTE If fromxml was not used
            pass

        return xml

    def fromxml(self, xml):
        """
        :param xml: Page set as XML element
        :type xml: lxml._Element
        :returns: bool
        :rtype: bool
        """

        if xml.tag == "Set":

            #------------------------------------------------------

            name = xml.get("Name")
            if name is not None:
                self.name = name

            #------------------------------------------------------

            for att in ["FirstPage", "Rows", "Columns"]:

                atx = xml.get(att)
                if atx is not None:

                    try:
                        atx = int(atx)
                    except ValueError:
                        raise exception.InsaneSLAValue(
                            "Page set @{} must be a number.".format(
                                atx
                            )
                        )

            first = xml.get("FirstPage")
            if first is not None:
                self.first_page = int(first)

            rows = xml.get("Rows")
            if rows is not None:
                self.rows = int(rows)

            columns = xml.get("Columns")
            if columns is not None:
                self.columns = int(columns)

            #--- Page names ---------------------------------------

            for child in xml:
                if child.tag == "PageNames":
                    n = child.get("Name")

                    if n not in self.pages:
                        self.pages.append(n)

            #--- FIXME This records undocumented attributes -------

            self.undocumented = undocumented_to_python(
                xml, ["GapBelow", "GapHorizontal", "GapVertical"]
            )

            return True
        else:
            return False

# vim:set shiftwidth=4 softtabstop=4 spl=en:
