prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  feat: Add `page_number` to meta of Documents in `DocumentSplitter`
  **Is your feature request related to a problem? Please describe.**
  In Haystack v1 we had an option in the Preprocessor to add the original `page_number` to a Document's meta data when it was split into a chunk. This feature made down stream applications of visualizing the retrieved text from original files (e.g. PDFs) very easy and straightforward, so I'd like to see it in Haystack v2 as well. 
  
  **Describe the solution you'd like**
  I would like to add the option to store the `page_number` in the meta info of the Document in the DocumentSplitter component. I believe we can use a similar/same implementation of calculating this like we did for the Preprocessor. 
  
  
  </issue>
  <code>
  [start of haystack/components/preprocessors/document_splitter.py]
  1 from copy import deepcopy
  2 from typing import List, Literal
  3 
  4 from more_itertools import windowed
  5 
  6 from haystack import Document, component
  7 
  8 
  9 @component
  10 class DocumentSplitter:
  11     """
  12     Splits a list of text documents into a list of text documents with shorter texts.
  13 
  14     Splitting documents with long texts is a common preprocessing step during indexing.
  15     This allows Embedders to create significant semantic representations
  16     and avoids exceeding the maximum context length of language models.
  17     """
  18 
  19     def __init__(
  20         self,
  21         split_by: Literal["word", "sentence", "page", "passage"] = "word",
  22         split_length: int = 200,
  23         split_overlap: int = 0,
  24     ):
  25         """
  26         Initialize the DocumentSplitter.
  27 
  28         :param split_by: The unit by which the document should be split. Choose from "word" for splitting by " ",
  29             "sentence" for splitting by ".", "page" for splitting by "\\f" or "passage" for splitting by "\\n\\n".
  30         :param split_length: The maximum number of units in each split.
  31         :param split_overlap: The number of units that each split should overlap.
  32         """
  33 
  34         self.split_by = split_by
  35         if split_by not in ["word", "sentence", "page", "passage"]:
  36             raise ValueError("split_by must be one of 'word', 'sentence', 'page' or 'passage'.")
  37         if split_length <= 0:
  38             raise ValueError("split_length must be greater than 0.")
  39         self.split_length = split_length
  40         if split_overlap < 0:
  41             raise ValueError("split_overlap must be greater than or equal to 0.")
  42         self.split_overlap = split_overlap
  43 
  44     @component.output_types(documents=List[Document])
  45     def run(self, documents: List[Document]):
  46         """
  47         Split documents into smaller parts.
  48 
  49         Splits documents by the unit expressed in `split_by`, with a length of `split_length`
  50         and an overlap of `split_overlap`.
  51 
  52         :param documents: The documents to split.
  53 
  54         :returns: A dictionary with the following key:
  55             - `documents`: List of documents with the split texts. A metadata field "source_id" is added to each
  56             document to keep track of the original document that was split. Other metadata are copied from the original
  57             document.
  58 
  59         :raises TypeError: if the input is not a list of Documents.
  60         :raises ValueError: if the content of a document is None.
  61         """
  62 
  63         if not isinstance(documents, list) or (documents and not isinstance(documents[0], Document)):
  64             raise TypeError("DocumentSplitter expects a List of Documents as input.")
  65 
  66         split_docs = []
  67         for doc in documents:
  68             if doc.content is None:
  69                 raise ValueError(
  70                     f"DocumentSplitter only works with text documents but document.content for document ID {doc.id} is None."
  71                 )
  72             units = self._split_into_units(doc.content, self.split_by)
  73             text_splits = self._concatenate_units(units, self.split_length, self.split_overlap)
  74             metadata = deepcopy(doc.meta)
  75             metadata["source_id"] = doc.id
  76             split_docs += [Document(content=txt, meta=metadata) for txt in text_splits]
  77         return {"documents": split_docs}
  78 
  79     def _split_into_units(self, text: str, split_by: Literal["word", "sentence", "passage", "page"]) -> List[str]:
  80         if split_by == "page":
  81             split_at = "\f"
  82         elif split_by == "passage":
  83             split_at = "\n\n"
  84         elif split_by == "sentence":
  85             split_at = "."
  86         elif split_by == "word":
  87             split_at = " "
  88         else:
  89             raise NotImplementedError(
  90                 "DocumentSplitter only supports 'word', 'sentence', 'page' or 'passage' split_by options."
  91             )
  92         units = text.split(split_at)
  93         # Add the delimiter back to all units except the last one
  94         for i in range(len(units) - 1):
  95             units[i] += split_at
  96         return units
  97 
  98     def _concatenate_units(self, elements: List[str], split_length: int, split_overlap: int) -> List[str]:
  99         """
  100         Concatenates the elements into parts of split_length units.
  101         """
  102         text_splits = []
  103         segments = windowed(elements, n=split_length, step=split_length - split_overlap)
  104         for seg in segments:
  105             current_units = [unit for unit in seg if unit is not None]
  106             txt = "".join(current_units)
  107             if len(txt) > 0:
  108                 text_splits.append(txt)
  109         return text_splits
  110
  [end of haystack/components/preprocessors/document_splitter.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/haystack/components/preprocessors/document_splitter.py b/haystack/components/preprocessors/document_splitter.py
  --- a/haystack/components/preprocessors/document_splitter.py
  +++ b/haystack/components/preprocessors/document_splitter.py
  @@ -1,5 +1,5 @@
   from copy import deepcopy
  -from typing import List, Literal
  +from typing import Dict, List, Literal, Tuple
   
   from more_itertools import windowed
   
  @@ -53,7 +53,7 @@
   
           :returns: A dictionary with the following key:
               - `documents`: List of documents with the split texts. A metadata field "source_id" is added to each
  -            document to keep track of the original document that was split. Other metadata are copied from the original
  +            document to keep track of the original document that was split. Another metadata field "page_number" is added to each number to keep track of the page it belonged to in the original document. Other metadata are copied from the original
               document.
   
           :raises TypeError: if the input is not a list of Documents.
  @@ -70,10 +70,12 @@
                       f"DocumentSplitter only works with text documents but document.content for document ID {doc.id} is None."
                   )
               units = self._split_into_units(doc.content, self.split_by)
  -            text_splits = self._concatenate_units(units, self.split_length, self.split_overlap)
  +            text_splits, splits_pages = self._concatenate_units(units, self.split_length, self.split_overlap)
               metadata = deepcopy(doc.meta)
               metadata["source_id"] = doc.id
  -            split_docs += [Document(content=txt, meta=metadata) for txt in text_splits]
  +            split_docs += self._create_docs_from_splits(
  +                text_splits=text_splits, splits_pages=splits_pages, meta=metadata
  +            )
           return {"documents": split_docs}
   
       def _split_into_units(self, text: str, split_by: Literal["word", "sentence", "passage", "page"]) -> List[str]:
  @@ -95,15 +97,40 @@
               units[i] += split_at
           return units
   
  -    def _concatenate_units(self, elements: List[str], split_length: int, split_overlap: int) -> List[str]:
  +    def _concatenate_units(
  +        self, elements: List[str], split_length: int, split_overlap: int
  +    ) -> Tuple[List[str], List[int]]:
           """
  -        Concatenates the elements into parts of split_length units.
  +        Concatenates the elements into parts of split_length units keeping track of the original page number that each element belongs.
           """
           text_splits = []
  +        splits_pages = []
  +        cur_page = 1
           segments = windowed(elements, n=split_length, step=split_length - split_overlap)
           for seg in segments:
               current_units = [unit for unit in seg if unit is not None]
               txt = "".join(current_units)
               if len(txt) > 0:
                   text_splits.append(txt)
  -        return text_splits
  +                splits_pages.append(cur_page)
  +                processed_units = current_units[: split_length - split_overlap]
  +                if self.split_by == "page":
  +                    num_page_breaks = len(processed_units)
  +                else:
  +                    num_page_breaks = sum(processed_unit.count("\f") for processed_unit in processed_units)
  +                cur_page += num_page_breaks
  +        return text_splits, splits_pages
  +
  +    @staticmethod
  +    def _create_docs_from_splits(text_splits: List[str], splits_pages: List[int], meta: Dict) -> List[Document]:
  +        """
  +        Creates Document objects from text splits enriching them with page number and the metadata of the original document.
  +        """
  +        documents: List[Document] = []
  +
  +        for i, txt in enumerate(text_splits):
  +            meta = deepcopy(meta)
  +            doc = Document(content=txt, meta=meta)
  +            doc.meta["page_number"] = splits_pages[i]
  +            documents.append(doc)
  +        return documents
  
verification_info: {"golden_diff": "diff --git a/haystack/components/preprocessors/document_splitter.py b/haystack/components/preprocessors/document_splitter.py\n--- a/haystack/components/preprocessors/document_splitter.py\n+++ b/haystack/components/preprocessors/document_splitter.py\n@@ -1,5 +1,5 @@\n from copy import deepcopy\n-from typing import List, Literal\n+from typing import Dict, List, Literal, Tuple\n \n from more_itertools import windowed\n \n@@ -53,7 +53,7 @@\n \n         :returns: A dictionary with the following key:\n             - `documents`: List of documents with the split texts. A metadata field \"source_id\" is added to each\n-            document to keep track of the original document that was split. Other metadata are copied from the original\n+            document to keep track of the original document that was split. Another metadata field \"page_number\" is added to each number to keep track of the page it belonged to in the original document. Other metadata are copied from the original\n             document.\n \n         :raises TypeError: if the input is not a list of Documents.\n@@ -70,10 +70,12 @@\n                     f\"DocumentSplitter only works with text documents but document.content for document ID {doc.id} is None.\"\n                 )\n             units = self._split_into_units(doc.content, self.split_by)\n-            text_splits = self._concatenate_units(units, self.split_length, self.split_overlap)\n+            text_splits, splits_pages = self._concatenate_units(units, self.split_length, self.split_overlap)\n             metadata = deepcopy(doc.meta)\n             metadata[\"source_id\"] = doc.id\n-            split_docs += [Document(content=txt, meta=metadata) for txt in text_splits]\n+            split_docs += self._create_docs_from_splits(\n+                text_splits=text_splits, splits_pages=splits_pages, meta=metadata\n+            )\n         return {\"documents\": split_docs}\n \n     def _split_into_units(self, text: str, split_by: Literal[\"word\", \"sentence\", \"passage\", \"page\"]) -> List[str]:\n@@ -95,15 +97,40 @@\n             units[i] += split_at\n         return units\n \n-    def _concatenate_units(self, elements: List[str], split_length: int, split_overlap: int) -> List[str]:\n+    def _concatenate_units(\n+        self, elements: List[str], split_length: int, split_overlap: int\n+    ) -> Tuple[List[str], List[int]]:\n         \"\"\"\n-        Concatenates the elements into parts of split_length units.\n+        Concatenates the elements into parts of split_length units keeping track of the original page number that each element belongs.\n         \"\"\"\n         text_splits = []\n+        splits_pages = []\n+        cur_page = 1\n         segments = windowed(elements, n=split_length, step=split_length - split_overlap)\n         for seg in segments:\n             current_units = [unit for unit in seg if unit is not None]\n             txt = \"\".join(current_units)\n             if len(txt) > 0:\n                 text_splits.append(txt)\n-        return text_splits\n+                splits_pages.append(cur_page)\n+                processed_units = current_units[: split_length - split_overlap]\n+                if self.split_by == \"page\":\n+                    num_page_breaks = len(processed_units)\n+                else:\n+                    num_page_breaks = sum(processed_unit.count(\"\\f\") for processed_unit in processed_units)\n+                cur_page += num_page_breaks\n+        return text_splits, splits_pages\n+\n+    @staticmethod\n+    def _create_docs_from_splits(text_splits: List[str], splits_pages: List[int], meta: Dict) -> List[Document]:\n+        \"\"\"\n+        Creates Document objects from text splits enriching them with page number and the metadata of the original document.\n+        \"\"\"\n+        documents: List[Document] = []\n+\n+        for i, txt in enumerate(text_splits):\n+            meta = deepcopy(meta)\n+            doc = Document(content=txt, meta=meta)\n+            doc.meta[\"page_number\"] = splits_pages[i]\n+            documents.append(doc)\n+        return documents\n", "issue": "feat: Add `page_number` to meta of Documents in `DocumentSplitter`\n**Is your feature request related to a problem? Please describe.**\r\nIn Haystack v1 we had an option in the Preprocessor to add the original `page_number` to a Document's meta data when it was split into a chunk. This feature made down stream applications of visualizing the retrieved text from original files (e.g. PDFs) very easy and straightforward, so I'd like to see it in Haystack v2 as well. \r\n\r\n**Describe the solution you'd like**\r\nI would like to add the option to store the `page_number` in the meta info of the Document in the DocumentSplitter component. I believe we can use a similar/same implementation of calculating this like we did for the Preprocessor. \r\n\n", "before_files": [{"content": "from copy import deepcopy\nfrom typing import List, Literal\n\nfrom more_itertools import windowed\n\nfrom haystack import Document, component\n\n\n@component\nclass DocumentSplitter:\n    \"\"\"\n    Splits a list of text documents into a list of text documents with shorter texts.\n\n    Splitting documents with long texts is a common preprocessing step during indexing.\n    This allows Embedders to create significant semantic representations\n    and avoids exceeding the maximum context length of language models.\n    \"\"\"\n\n    def __init__(\n        self,\n        split_by: Literal[\"word\", \"sentence\", \"page\", \"passage\"] = \"word\",\n        split_length: int = 200,\n        split_overlap: int = 0,\n    ):\n        \"\"\"\n        Initialize the DocumentSplitter.\n\n        :param split_by: The unit by which the document should be split. Choose from \"word\" for splitting by \" \",\n            \"sentence\" for splitting by \".\", \"page\" for splitting by \"\\\\f\" or \"passage\" for splitting by \"\\\\n\\\\n\".\n        :param split_length: The maximum number of units in each split.\n        :param split_overlap: The number of units that each split should overlap.\n        \"\"\"\n\n        self.split_by = split_by\n        if split_by not in [\"word\", \"sentence\", \"page\", \"passage\"]:\n            raise ValueError(\"split_by must be one of 'word', 'sentence', 'page' or 'passage'.\")\n        if split_length <= 0:\n            raise ValueError(\"split_length must be greater than 0.\")\n        self.split_length = split_length\n        if split_overlap < 0:\n            raise ValueError(\"split_overlap must be greater than or equal to 0.\")\n        self.split_overlap = split_overlap\n\n    @component.output_types(documents=List[Document])\n    def run(self, documents: List[Document]):\n        \"\"\"\n        Split documents into smaller parts.\n\n        Splits documents by the unit expressed in `split_by`, with a length of `split_length`\n        and an overlap of `split_overlap`.\n\n        :param documents: The documents to split.\n\n        :returns: A dictionary with the following key:\n            - `documents`: List of documents with the split texts. A metadata field \"source_id\" is added to each\n            document to keep track of the original document that was split. Other metadata are copied from the original\n            document.\n\n        :raises TypeError: if the input is not a list of Documents.\n        :raises ValueError: if the content of a document is None.\n        \"\"\"\n\n        if not isinstance(documents, list) or (documents and not isinstance(documents[0], Document)):\n            raise TypeError(\"DocumentSplitter expects a List of Documents as input.\")\n\n        split_docs = []\n        for doc in documents:\n            if doc.content is None:\n                raise ValueError(\n                    f\"DocumentSplitter only works with text documents but document.content for document ID {doc.id} is None.\"\n                )\n            units = self._split_into_units(doc.content, self.split_by)\n            text_splits = self._concatenate_units(units, self.split_length, self.split_overlap)\n            metadata = deepcopy(doc.meta)\n            metadata[\"source_id\"] = doc.id\n            split_docs += [Document(content=txt, meta=metadata) for txt in text_splits]\n        return {\"documents\": split_docs}\n\n    def _split_into_units(self, text: str, split_by: Literal[\"word\", \"sentence\", \"passage\", \"page\"]) -> List[str]:\n        if split_by == \"page\":\n            split_at = \"\\f\"\n        elif split_by == \"passage\":\n            split_at = \"\\n\\n\"\n        elif split_by == \"sentence\":\n            split_at = \".\"\n        elif split_by == \"word\":\n            split_at = \" \"\n        else:\n            raise NotImplementedError(\n                \"DocumentSplitter only supports 'word', 'sentence', 'page' or 'passage' split_by options.\"\n            )\n        units = text.split(split_at)\n        # Add the delimiter back to all units except the last one\n        for i in range(len(units) - 1):\n            units[i] += split_at\n        return units\n\n    def _concatenate_units(self, elements: List[str], split_length: int, split_overlap: int) -> List[str]:\n        \"\"\"\n        Concatenates the elements into parts of split_length units.\n        \"\"\"\n        text_splits = []\n        segments = windowed(elements, n=split_length, step=split_length - split_overlap)\n        for seg in segments:\n            current_units = [unit for unit in seg if unit is not None]\n            txt = \"\".join(current_units)\n            if len(txt) > 0:\n                text_splits.append(txt)\n        return text_splits\n", "path": "haystack/components/preprocessors/document_splitter.py"}]}