from __future__ import annotations import re from typing import Any, Dict, List, Tuple, TypedDict, Union from langchain_core.documents import Document from langchain_text_splitters.base import Language from langchain_text_splitters.character import RecursiveCharacterTextSplitter
class LineType(TypedDict):
"""Line type as typed dict.""" metadata: Dict[str, str] content: str
class HeaderType(TypedDict):
"""Header type as typed dict.""" level: int name: str data: str
class IdentifyHeaders:
"""Compute data for identifying header text.""" def __init__(self,pdf_doc: str,page = None,body_limit: float = 10): """Read all text and make a dictionary of fontsizes. Args: body_limit: consider text with larger font size as some header """ mydoc = fitz.open(pdf_doc) fontsizes = {} pages = range(mydoc.page_count) for pno in pages: page = mydoc.load_page(pno) blocks = page.get_text("dict", flags=fitz.TEXTFLAGS_TEXT)["blocks"] for span in [ # look at all non-empty horizontal spans s for b in blocks for l in b["lines"] for s in l["spans"] if not is_white(s["text"]) ]: fontsz = round(span["size"]) count = fontsizes.get(fontsz, 0) + len(span["text"].strip()) fontsizes[fontsz] = count
mydoc.close() self.header_id = {} temp = sorted([(k, v) for k, v in fontsizes.items()],key=lambda i: i[1],reverse=True,) b_limit = temp[0][0] sizes = sorted([f for f in fontsizes.keys() if f > b_limit],reverse=True,)[:8] for i, size in enumerate(sizes): self.header_id[size] = "#" * (i + 1) + " "
def get_header_id(self, span: dict, page=None) -> str: """Return appropriate markdown header prefix. Given a text span from a "dict"/"rawdict" extraction, determine the markdown header prefix string of 0 to n concatenated '#' characters. """ fontsize = round(span["size"]) # compute fontsize hdr_id = self.header_id.get(fontsize, "") return hdr_id
def aggregate_lines_to_chunks(lines: List[LineType]) -> List[Document]:
"""Combine lines with common metadata into chunks Args: lines: Line of text / associated header metadata """
def split_text(text: str,headers_split: List[Tuple[str, str]]) -> List[Document]:
"""Split markdown file Args: text: Markdown file""" lines = text.split("\n") lines_with_metadata: List[LineType] = [] current_content: List[str] = [] current_metadata: Dict[str, str] = {} current_metadata['type'] = 'text' header_stack: List[HeaderType] = [] initial_metadata: Dict[str, str] = {} in_code_block = False opening_fence = "" for line in lines: stripped_line = line.strip() stripped_line = "".join(filter(str.isprintable, stripped_line)) if stripped_line == : continue current_header_level = 0 if stripped_line.startswith("-"): continue elif stripped_line.startswith("```") or stripped_line.startswith("84.185.107.48"): initial_metadata['type'] = 'code' in_code_block = True opening_fence = "```" elif stripped_line.startswith("|"): initial_metadata['type'] = 'table' elif not in_code_block: initial_metadata['type'] = 'text' if in_code_block: if stripped_line.startswith(opening_fence): in_code_block = False opening_fence = ""
for sep, name in headers_split: #if header create index if stripped_line.startswith(sep) and (len(stripped_line) == len(sep) or stripped_line[len(sep)] == " "): current_header_level = sep.count("#") while (header_stack and header_stack[-1]["level"] >= current_header_level): popped_header = header_stack.pop() if popped_header["name"] in initial_metadata: initial_metadata.pop(popped_header["name"])
header: HeaderType = {"level": current_header_level,"name": name,"data": stripped_line[len(sep):].strip()} header_stack.append(header) initial_metadata[name] = header["data"]
if current_metadata['type'] != initial_metadata['type']: lines_with_metadata.append({"content":"\n".join(current_content),"metadata":current_metadata.copy()}) current_content.clear() current_metadata = initial_metadata.copy() if current_header_level == 0: current_content.append(stripped_line) else: lines_with_metadata.append({"content":"\n".join(current_content),"metadata":current_metadata.copy()}) current_content.clear()
lines_with_metadata.append({"content":"\n".join(current_content),"metadata":current_metadata.copy()}) #lines_with_metadata.append({"content":"\n".join(current_content),"metadata":current_metadata}) aggregated_chunks = [x for x in lines_with_metadata if x['content'] != ] # aggregated_chunks: List[LineType] = [] # for line in lines_with_metadata: # if (aggregated_chunks and aggregated_chunks[-1]["metadata"] == line["metadata"]): # aggregated_chunks[-1]["content"] += " \n" + line["content"] # elif (aggregated_chunks # and aggregated_chunks[-1]["metadata"] != line["metadata"] # and len(aggregated_chunks[-1]["metadata"]) < len(line["metadata"]) # and aggregated_chunks[-1]["content"].split("\n")[-1][0] == "#" # and False # ): # aggregated_chunks[-1]["content"] += " \n" + line["content"] # aggregated_chunks[-1]["metadata"] = line["metadata"] # else: # aggregated_chunks.append(line)
return [ Document(page_content=chunk["content"], metadata=chunk["metadata"]) for chunk in aggregated_chunks ]