pyWebLayout/examples/recursive_position_demo.py
Duncan Tourolle 65ab46556f
Some checks failed
Python CI / test (push) Failing after 3m55s
big update with ok rendering
2025-08-27 22:22:54 +02:00

387 lines
13 KiB
Python

#!/usr/bin/env python3
"""
Demonstration of the Recursive Position System
This example shows how to use the hierarchical position tracking system
that can reference any type of content (words, images, table cells, etc.)
in a nested document structure.
Key Features Demonstrated:
- Hierarchical position tracking
- Dynamic content type support
- JSON and shelf serialization
- Position relationships (ancestor/descendant)
- Bookmark management
- Real-world ereader scenarios
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from pyWebLayout.layout.recursive_position import (
ContentType, LocationNode, RecursivePosition, PositionBuilder, PositionStorage,
create_word_position, create_image_position, create_table_cell_position, create_list_item_position
)
def demonstrate_basic_position_creation():
"""Show basic position creation and manipulation"""
print("=== Basic Position Creation ===")
# Create a position using the builder pattern
position = (PositionBuilder()
.chapter(2)
.block(5)
.paragraph()
.word(12, offset=3)
.with_rendering_metadata(font_scale=1.5, page_size=[800, 600])
.build())
print(f"Position path: {position}")
print(f"Depth: {position.get_depth()}")
print(f"Leaf node: {position.get_leaf_node()}")
# Query specific nodes
chapter_node = position.get_node(ContentType.CHAPTER)
word_node = position.get_node(ContentType.WORD)
print(f"Chapter: {chapter_node.index}")
print(f"Word: {word_node.index}, offset: {word_node.offset}")
print(f"Font scale: {position.rendering_metadata.get('font_scale')}")
print()
def demonstrate_different_content_types():
"""Show positions for different content types"""
print("=== Different Content Types ===")
# Word position
word_pos = create_word_position(1, 3, 15, 2)
print(f"Word position: {word_pos}")
# Image position
image_pos = create_image_position(2, 1, 0)
print(f"Image position: {image_pos}")
# Table cell position
table_pos = create_table_cell_position(0, 4, 2, 1, 5)
print(f"Table cell position: {table_pos}")
# List item position
list_pos = create_list_item_position(1, 2, 3, 0)
print(f"List item position: {list_pos}")
# Complex nested structure
complex_pos = (PositionBuilder()
.chapter(3)
.block(7)
.table(0, table_type="data", columns=4)
.table_row(2, row_type="header")
.table_cell(1, cell_type="data", colspan=2)
.link(0, url="https://example.com", text="Click here")
.build())
print(f"Complex nested position: {complex_pos}")
print()
def demonstrate_position_relationships():
"""Show ancestor/descendant relationships"""
print("=== Position Relationships ===")
# Create related positions
chapter_pos = (PositionBuilder()
.chapter(1)
.block(2)
.build())
paragraph_pos = (PositionBuilder()
.chapter(1)
.block(2)
.paragraph()
.build())
word_pos = (PositionBuilder()
.chapter(1)
.block(2)
.paragraph()
.word(5)
.build())
# Test relationships
print(f"Chapter position: {chapter_pos}")
print(f"Paragraph position: {paragraph_pos}")
print(f"Word position: {word_pos}")
print(f"Chapter is ancestor of paragraph: {chapter_pos.is_ancestor_of(paragraph_pos)}")
print(f"Chapter is ancestor of word: {chapter_pos.is_ancestor_of(word_pos)}")
print(f"Word is descendant of chapter: {word_pos.is_descendant_of(chapter_pos)}")
# Find common ancestors
unrelated_pos = create_word_position(2, 1, 0) # Different chapter
common = word_pos.get_common_ancestor(unrelated_pos)
print(f"Common ancestor of word and unrelated: {common}")
print()
def demonstrate_serialization():
"""Show JSON and shelf serialization"""
print("=== Serialization ===")
# Create a complex position
position = (PositionBuilder()
.chapter(4)
.block(8)
.table(0, table_type="financial", columns=5, rows=20)
.table_row(3, row_type="data", category="Q2")
.table_cell(2, cell_type="currency", format="USD")
.word(0, text="$1,234.56")
.with_rendering_metadata(
font_scale=1.2,
page_size=[600, 800],
theme="light",
currency_format="USD"
)
.build())
# JSON serialization
json_str = position.to_json()
print("JSON serialization:")
print(json_str[:200] + "..." if len(json_str) > 200 else json_str)
# Deserialize and verify
restored = RecursivePosition.from_json(json_str)
print(f"Restored position equals original: {position == restored}")
print()
def demonstrate_storage_systems():
"""Show both JSON and shelf storage"""
print("=== Storage Systems ===")
# Create test positions
positions = {
"bookmark1": create_word_position(1, 5, 20, 3),
"bookmark2": create_image_position(2, 3, 1),
"bookmark3": create_table_cell_position(3, 1, 2, 1, 0)
}
# Test JSON storage
print("JSON Storage:")
json_storage = PositionStorage("demo_positions_json", use_shelf=False)
for name, pos in positions.items():
json_storage.save_position("demo_doc", name, pos)
print(f" Saved {name}: {pos}")
# List and load positions
saved_positions = json_storage.list_positions("demo_doc")
print(f" Saved positions: {saved_positions}")
loaded = json_storage.load_position("demo_doc", "bookmark1")
print(f" Loaded bookmark1: {loaded}")
print(f" Matches original: {loaded == positions['bookmark1']}")
# Test shelf storage
print("\nShelf Storage:")
shelf_storage = PositionStorage("demo_positions_shelf", use_shelf=True)
for name, pos in positions.items():
shelf_storage.save_position("demo_doc", name, pos)
shelf_positions = shelf_storage.list_positions("demo_doc")
print(f" Shelf positions: {shelf_positions}")
# Clean up demo files
import shutil
try:
shutil.rmtree("demo_positions_json")
shutil.rmtree("demo_positions_shelf")
except:
pass
print()
def demonstrate_ereader_scenario():
"""Show realistic ereader bookmark scenario"""
print("=== Ereader Bookmark Scenario ===")
# Simulate user reading progress
reading_positions = [
# User starts reading chapter 1
(PositionBuilder()
.chapter(1)
.block(0)
.paragraph()
.word(0)
.with_rendering_metadata(font_scale=1.0, page_size=[600, 800], theme="light")
.build(), "Chapter 1 Start"),
# User bookmarks an interesting quote in chapter 2
(PositionBuilder()
.chapter(2)
.block(15)
.paragraph()
.word(8, offset=0)
.with_rendering_metadata(font_scale=1.2, page_size=[600, 800], theme="sepia")
.build(), "Interesting Quote"),
# User bookmarks a table in chapter 3
(PositionBuilder()
.chapter(3)
.block(22)
.table(0, table_type="data", title="Sales Figures")
.table_row(1, row_type="header")
.table_cell(0, cell_type="header", text="Quarter")
.with_rendering_metadata(font_scale=1.1, page_size=[600, 800], theme="dark")
.build(), "Sales Table"),
# User bookmarks an image caption
(PositionBuilder()
.chapter(4)
.block(8)
.image(0, alt_text="Company Logo", caption="Figure 4.1: Corporate Identity")
.with_rendering_metadata(font_scale=1.0, page_size=[600, 800], theme="light")
.build(), "Logo Image"),
# User's current reading position (with character-level precision)
(PositionBuilder()
.chapter(5)
.block(12)
.paragraph()
.word(23, offset=7) # 7 characters into word 23
.with_rendering_metadata(font_scale=1.3, page_size=[600, 800], theme="dark")
.build(), "Current Position")
]
# Save all bookmarks
storage = PositionStorage("ereader_bookmarks", use_shelf=False)
for position, description in reading_positions:
bookmark_name = description.lower().replace(" ", "_")
storage.save_position("my_novel", bookmark_name, position)
print(f"Saved bookmark '{description}': {position}")
print(f"\nTotal bookmarks: {len(storage.list_positions('my_novel'))}")
# Demonstrate bookmark navigation
print("\n--- Bookmark Navigation ---")
current_pos = reading_positions[-1][0] # Current reading position
for position, description in reading_positions[:-1]: # All except current
# Calculate relationship to current position
if position.is_ancestor_of(current_pos):
relationship = "ancestor of current"
elif current_pos.is_ancestor_of(position):
relationship = "descendant of current"
else:
common = position.get_common_ancestor(current_pos)
if len(common.path) > 1:
relationship = f"shares {common.get_leaf_node().content_type.value} with current"
else:
relationship = "unrelated to current"
print(f"'{description}' is {relationship}")
# Clean up
try:
shutil.rmtree("ereader_bookmarks")
except:
pass
print()
def demonstrate_advanced_navigation():
"""Show advanced navigation scenarios"""
print("=== Advanced Navigation Scenarios ===")
# Multi-level list navigation
print("Multi-level List Navigation:")
nested_list_pos = (PositionBuilder()
.chapter(2)
.block(5)
.list(0, list_type="ordered", title="Main Topics")
.list_item(2, text="Data Structures")
.list(1, list_type="unordered", title="Subtopics")
.list_item(1, text="Hash Tables")
.word(3, text="implementation")
.build())
print(f" Nested list position: {nested_list_pos}")
# Navigate to parent list item
parent_item_pos = nested_list_pos.copy().truncate_to_type(ContentType.LIST_ITEM)
print(f" Parent list item: {parent_item_pos}")
# Navigate to main list
main_list_pos = nested_list_pos.copy().truncate_to_type(ContentType.LIST)
print(f" Main list: {main_list_pos}")
# Table navigation
print("\nTable Navigation:")
table_pos = (PositionBuilder()
.chapter(3)
.block(10)
.table(0, table_type="comparison", rows=5, columns=3)
.table_row(2, row_type="data")
.table_cell(1, cell_type="data", header="Price")
.word(0, text="$99.99")
.build())
print(f" Table cell position: {table_pos}")
# Navigate to different cells in same row
next_cell_pos = table_pos.copy()
cell_node = next_cell_pos.get_node(ContentType.TABLE_CELL)
cell_node.index = 2 # Move to next column
cell_node.metadata["header"] = "Quantity"
word_node = next_cell_pos.get_node(ContentType.WORD)
word_node.text = "5"
print(f" Next cell position: {next_cell_pos}")
# Verify they share the same row
common = table_pos.get_common_ancestor(next_cell_pos)
row_node = common.get_node(ContentType.TABLE_ROW)
print(f" Shared row index: {row_node.index if row_node else 'None'}")
print()
def main():
"""Run all demonstrations"""
print("Recursive Position System Demonstration")
print("=" * 50)
print()
demonstrate_basic_position_creation()
demonstrate_different_content_types()
demonstrate_position_relationships()
demonstrate_serialization()
demonstrate_storage_systems()
demonstrate_ereader_scenario()
demonstrate_advanced_navigation()
print("=== Summary ===")
print("The Recursive Position System provides:")
print("✓ Hierarchical position tracking for any content type")
print("✓ Dynamic content type support (words, images, tables, lists, etc.)")
print("✓ Flexible serialization (JSON and Python shelf)")
print("✓ Position relationships (ancestor/descendant queries)")
print("✓ Fluent builder pattern for easy position creation")
print("✓ Metadata support for rendering context")
print("✓ Real-world ereader bookmark management")
print("✓ Advanced navigation capabilities")
print()
print("This system is ideal for:")
print("• Ereader applications with precise bookmarking")
print("• Document editors with complex navigation")
print("• Content management systems")
print("• Any application requiring hierarchical position tracking")
if __name__ == "__main__":
main()