This commit is contained in:
@@ -56,10 +56,6 @@ These examples demonstrate rendering HTML content to multi-page layouts:
|
||||
|
||||
For detailed information about HTML rendering, see `README_HTML_MULTIPAGE.md`.
|
||||
|
||||
### Advanced Topics
|
||||
|
||||
**`recursive_position_demo.py`** - Demonstrates the recursive position tracking system
|
||||
|
||||
## Documentation
|
||||
|
||||
- `README_EREADER.md` - Detailed EbookReader API documentation
|
||||
|
||||
@@ -1,386 +0,0 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user