better parsing using handlers
Python CI / test (push) Failing after 21s

This commit is contained in:
2025-06-07 14:38:11 +02:00
parent 81d85386c5
commit ba6d8ca906
15 changed files with 1189 additions and 97 deletions
+4 -4
View File
@@ -6,7 +6,7 @@ Tests the core abstract block classes that form the foundation of the document m
import unittest
from pyWebLayout.abstract.block import (
Block, BlockType, Parapgraph, Heading, HeadingLevel, Quote, CodeBlock,
Block, BlockType, Paragraph, Heading, HeadingLevel, Quote, CodeBlock,
HList, ListStyle, ListItem, Table, TableRow, TableCell,
HorizontalRule, LineBreak, Image
)
@@ -19,7 +19,7 @@ class TestBlockElements(unittest.TestCase):
def test_paragraph_creation(self):
"""Test creating and using paragraphs."""
paragraph = Parapgraph()
paragraph = Paragraph()
self.assertEqual(paragraph.block_type, BlockType.PARAGRAPH)
self.assertEqual(paragraph.word_count, 0)
@@ -62,8 +62,8 @@ class TestBlockElements(unittest.TestCase):
quote = Quote()
# Add nested paragraphs
p1 = Parapgraph()
p2 = Parapgraph()
p1 = Paragraph()
p2 = Paragraph()
quote.add_block(p1)
quote.add_block(p2)
+10 -10
View File
@@ -7,7 +7,7 @@ document structure and metadata management.
import unittest
from pyWebLayout.abstract.document import Document, Chapter, Book, MetadataType
from pyWebLayout.abstract.block import Parapgraph, Heading, HeadingLevel, BlockType
from pyWebLayout.abstract.block import Paragraph, Heading, HeadingLevel, BlockType
from pyWebLayout.abstract.inline import Word, FormattedSpan
from pyWebLayout.style import Font
@@ -77,8 +77,8 @@ class TestDocument(unittest.TestCase):
def test_block_management(self):
"""Test adding and managing blocks."""
# Create some blocks
para1 = Parapgraph()
para2 = Parapgraph()
para1 = Paragraph()
para2 = Paragraph()
heading = Heading(HeadingLevel.H1)
# Add blocks
@@ -95,7 +95,7 @@ class TestDocument(unittest.TestCase):
def test_anchor_management(self):
"""Test named anchor functionality."""
heading = Heading(HeadingLevel.H1)
para = Parapgraph()
para = Paragraph()
# Add anchors
self.doc.add_anchor("intro", heading)
@@ -154,8 +154,8 @@ class TestDocument(unittest.TestCase):
def test_find_blocks_by_type(self):
"""Test finding blocks by type."""
# Create blocks of different types
para1 = Parapgraph()
para2 = Parapgraph()
para1 = Paragraph()
para2 = Paragraph()
heading1 = Heading(HeadingLevel.H1)
heading2 = Heading(HeadingLevel.H2)
@@ -180,7 +180,7 @@ class TestDocument(unittest.TestCase):
def test_find_headings(self):
"""Test finding heading blocks specifically."""
# Create mixed blocks
para = Parapgraph()
para = Paragraph()
h1 = Heading(HeadingLevel.H1)
h2 = Heading(HeadingLevel.H2)
@@ -284,8 +284,8 @@ class TestChapter(unittest.TestCase):
def test_block_management(self):
"""Test adding blocks to chapter."""
para1 = Parapgraph()
para2 = Parapgraph()
para1 = Paragraph()
para2 = Paragraph()
heading = Heading(HeadingLevel.H2)
# Add blocks
@@ -450,7 +450,7 @@ class TestBook(unittest.TestCase):
"""Test that Book inherits all Document functionality."""
# Test that book can use all document methods
# Add blocks directly to book
para = Parapgraph()
para = Paragraph()
self.book.add_block(para)
self.assertEqual(len(self.book.blocks), 1)
-44
View File
@@ -1,44 +0,0 @@
#!/usr/bin/env python3
"""
Simple test script to verify that the EPUB reader fixes are working correctly.
"""
import sys
import os
# Add the pyWebLayout directory to the Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'pyWebLayout'))
try:
from pyWebLayout.io.readers.epub_reader import read_epub
print("Successfully imported epub_reader module")
# Test reading the EPUB file
epub_path = os.path.join('pyWebLayout', 'examples', 'pg174-images-3.epub')
if not os.path.exists(epub_path):
print(f"EPUB file not found: {epub_path}")
sys.exit(1)
print(f"Reading EPUB file: {epub_path}")
# Try to read the EPUB
book = read_epub(epub_path)
print(f"Successfully read EPUB file!")
print(f"Book title: {book.title}")
print(f"Number of chapters: {len(book.chapters)}")
# Check first chapter
if book.chapters:
first_chapter = book.chapters[0]
print(f"First chapter title: {first_chapter.title}")
print(f"First chapter has {len(first_chapter.blocks)} blocks")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
print("Test completed successfully!")
+9 -9
View File
@@ -9,7 +9,7 @@ import unittest
from pyWebLayout.io.readers.html_content import HTMLContentReader
from pyWebLayout.abstract.document import Document
from pyWebLayout.abstract.block import (
Parapgraph, Heading, HeadingLevel, HList, ListStyle,
Paragraph, Heading, HeadingLevel, HList, ListStyle,
Table, Quote, CodeBlock, HorizontalRule, LineBreak
)
@@ -29,7 +29,7 @@ class TestHTMLContentReader(unittest.TestCase):
result = self.reader.extract_content(html, self.document)
self.assertEqual(len(self.document.blocks), 1)
self.assertIsInstance(self.document.blocks[0], Parapgraph)
self.assertIsInstance(self.document.blocks[0], Paragraph)
paragraph = self.document.blocks[0]
words = list(paragraph.words())
@@ -107,7 +107,7 @@ class TestHTMLContentReader(unittest.TestCase):
# Check first item content
first_item_blocks = list(items[0].blocks())
self.assertEqual(len(first_item_blocks), 1)
self.assertIsInstance(first_item_blocks[0], Parapgraph)
self.assertIsInstance(first_item_blocks[0], Paragraph)
def test_ordered_list(self):
"""Test parsing ordered lists."""
@@ -202,8 +202,8 @@ class TestHTMLContentReader(unittest.TestCase):
quote = self.document.blocks[0]
quote_blocks = list(quote.blocks())
self.assertEqual(len(quote_blocks), 2)
self.assertIsInstance(quote_blocks[0], Parapgraph)
self.assertIsInstance(quote_blocks[1], Parapgraph)
self.assertIsInstance(quote_blocks[0], Paragraph)
self.assertIsInstance(quote_blocks[1], Paragraph)
def test_code_block(self):
"""Test parsing code blocks."""
@@ -229,9 +229,9 @@ def hello():
self.reader.extract_content(html, self.document)
self.assertEqual(len(self.document.blocks), 3)
self.assertIsInstance(self.document.blocks[0], Parapgraph)
self.assertIsInstance(self.document.blocks[0], Paragraph)
self.assertIsInstance(self.document.blocks[1], HorizontalRule)
self.assertIsInstance(self.document.blocks[2], Parapgraph)
self.assertIsInstance(self.document.blocks[2], Paragraph)
def test_html_entities(self):
"""Test handling HTML entities."""
@@ -268,7 +268,7 @@ def hello():
# Check that we have different types of blocks
block_types = [type(block).__name__ for block in self.document.blocks]
self.assertIn('Parapgraph', block_types) # From div
self.assertIn('Paragraph', block_types) # From div
self.assertIn('Heading', block_types)
self.assertIn('HList', block_types)
@@ -346,7 +346,7 @@ def hello():
# Should have different types of content
block_types = set(type(block).__name__ for block in self.document.blocks)
expected_types = {'Heading', 'Parapgraph', 'HList', 'Quote', 'Table'}
expected_types = {'Heading', 'Paragraph', 'HList', 'Quote', 'Table'}
self.assertTrue(expected_types.issubset(block_types))
+384
View File
@@ -0,0 +1,384 @@
"""
Unit tests for HTML extraction functionality.
Tests the HTML parsing and conversion to pyWebLayout abstract elements,
including styled content within paragraphs and block-level elements.
"""
import unittest
from pyWebLayout.io.readers.html_extraction import parse_html_string
from pyWebLayout.abstract.block import Paragraph, Heading, HeadingLevel, Quote, CodeBlock, HList, ListStyle, Table
from pyWebLayout.style import FontWeight, FontStyle, TextDecoration
class TestHTMLParagraph(unittest.TestCase):
"""Test cases for basic paragraph parsing."""
def test_simple(self):
text = "<p>This is a paragraph.</p>"
paragraphs = parse_html_string(text)
self.assertEqual(len(paragraphs), 1)
self.assertEqual(len(paragraphs[0]), 4)
for w1, t1 in zip(paragraphs[0].words(), "This is a paragraph.".split(" ")):
self.assertEqual(w1[1].text, t1)
def test_multiple(self):
text = "<p>This is a paragraph.</p><p>This is another paragraph.</p>"
paragraphs = parse_html_string(text)
self.assertEqual(len(paragraphs), 2)
self.assertEqual(len(paragraphs[0]), 4)
self.assertEqual(len(paragraphs[1]), 4)
for w1, t1 in zip(paragraphs[0].words(), "This is a paragraph.".split(" ")):
self.assertEqual(w1[1].text, t1)
for w1, t1 in zip(paragraphs[1].words(), "This is another paragraph.".split(" ")):
self.assertEqual(w1[1].text, t1)
class TestHTMLStyledParagraphs(unittest.TestCase):
"""Test cases for paragraphs with inline styling."""
def test_bold_text(self):
"""Test paragraphs with bold text using <strong> and <b> tags."""
text = "<p>This is <strong>bold text</strong> in a paragraph.</p>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
self.assertIsInstance(blocks[0], Paragraph)
words = list(blocks[0].words())
self.assertEqual(len(words), 7) # "This is bold text in a paragraph."
# Check that 'bold' and 'text' words have bold font weight
bold_word = words[2][1] # 'bold'
text_word = words[3][1] # 'text'
self.assertEqual(bold_word.text, "bold")
self.assertEqual(bold_word.style.weight, FontWeight.BOLD)
self.assertEqual(text_word.text, "text")
self.assertEqual(text_word.style.weight, FontWeight.BOLD)
# Check that other words are not bold
normal_word = words[0][1] # 'This'
self.assertEqual(normal_word.text, "This")
self.assertNotEqual(normal_word.style.weight, FontWeight.BOLD)
def test_italic_text(self):
"""Test paragraphs with italic text using <em> and <i> tags."""
text = "<p>This is <em>italic text</em> in a paragraph.</p>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
self.assertIsInstance(blocks[0], Paragraph)
words = list(blocks[0].words())
# Check that 'italic' and 'text' words have italic font style
italic_word = words[2][1] # 'italic'
text_word = words[3][1] # 'text'
self.assertEqual(italic_word.text, "italic")
self.assertEqual(italic_word.style.style, FontStyle.ITALIC)
self.assertEqual(text_word.text, "text")
self.assertEqual(text_word.style.style, FontStyle.ITALIC)
def test_underlined_text(self):
"""Test paragraphs with underlined text using <u> tag."""
text = "<p>This is <u>underlined text</u> here.</p>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
words = list(blocks[0].words())
underlined_word = words[2][1] # 'underlined'
self.assertEqual(underlined_word.style.decoration, TextDecoration.UNDERLINE)
def test_strikethrough_text(self):
"""Test paragraphs with strikethrough text using <s> and <del> tags."""
text = "<p>This is <s>strikethrough text</s> here.</p>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
words = list(blocks[0].words())
strike_word = words[2][1] # 'strikethrough'
self.assertEqual(strike_word.style.decoration, TextDecoration.STRIKETHROUGH)
def test_span_with_inline_styles(self):
"""Test paragraphs with span elements containing inline CSS styles."""
text = '<p>This text is normal, but <span style="color: red; font-weight: bold;">this part is red and bold</span>.</p>'
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
self.assertIsInstance(blocks[0], Paragraph)
words = list(blocks[0].words())
# Find the styled words
styled_words = []
for _, word in words:
if word.text in ["this", "part", "is", "red", "and", "bold"]:
if word.style.weight == FontWeight.BOLD:
styled_words.append(word)
self.assertGreater(len(styled_words), 0, "Should have bold words in styled span")
# Check that at least one word has the red color
red_words = [w for w in styled_words if w.style.colour == (255, 0, 0)]
self.assertGreater(len(red_words), 0, "Should have red colored words")
def test_mixed_formatting(self):
"""Test paragraphs with multiple formatting elements combined."""
text = "<p>This paragraph contains <strong>bold</strong>, <em>italic</em>, <span style=\"color: blue;\">blue</span>, and <mark>highlighted</mark> text all together.</p>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
self.assertIsInstance(blocks[0], Paragraph)
words = list(blocks[0].words())
# Check for bold word
bold_words = [w for _, w in words if w.style.weight == FontWeight.BOLD]
self.assertGreater(len(bold_words), 0, "Should have bold words")
# Check for italic word
italic_words = [w for _, w in words if w.style.style == FontStyle.ITALIC]
self.assertGreater(len(italic_words), 0, "Should have italic words")
# Check for blue colored word
blue_words = [w for _, w in words if w.style.colour == (0, 0, 255)]
self.assertGreater(len(blue_words), 0, "Should have blue colored words")
def test_nested_formatting(self):
"""Test nested formatting elements."""
text = "<p>This has <strong>bold with <em>italic inside</em></strong> formatting.</p>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
words = list(blocks[0].words())
# Find words that should be both bold and italic
bold_italic_words = [w for _, w in words
if w.style.weight == FontWeight.BOLD and w.style.style == FontStyle.ITALIC]
self.assertGreater(len(bold_italic_words), 0, "Should have words that are both bold and italic")
def test_color_variations(self):
"""Test different color formats in CSS."""
text = '<p><span style="color: #ff0000;">Hex red</span> and <span style="color: green;">Named green</span>.</p>'
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
words = list(blocks[0].words())
# Check for hex red color
hex_red_words = [w for _, w in words if w.style.colour == (255, 0, 0)]
self.assertGreater(len(hex_red_words), 0, "Should have hex red colored words")
# Check for named green color
green_words = [w for _, w in words if w.style.colour == (0, 255, 0)]
self.assertGreater(len(green_words), 0, "Should have green colored words")
class TestHTMLBlockElements(unittest.TestCase):
"""Test cases for block-level HTML elements."""
def test_body_element(self):
"""Test parsing of body element containing other elements."""
text = "<body><p>Paragraph one.</p><p>Paragraph two.</p></body>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 2)
self.assertIsInstance(blocks[0], Paragraph)
self.assertIsInstance(blocks[1], Paragraph)
def test_div_container(self):
"""Test div elements as generic containers."""
text = "<div><p>First paragraph.</p><p>Second paragraph.</p></div>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 2)
self.assertIsInstance(blocks[0], Paragraph)
self.assertIsInstance(blocks[1], Paragraph)
def test_headings(self):
"""Test all heading levels h1-h6."""
text = "<h1>Heading 1</h1><h2>Heading 2</h2><h3>Heading 3</h3><h4>Heading 4</h4><h5>Heading 5</h5><h6>Heading 6</h6>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 6)
expected_levels = [HeadingLevel.H1, HeadingLevel.H2, HeadingLevel.H3,
HeadingLevel.H4, HeadingLevel.H5, HeadingLevel.H6]
for i, block in enumerate(blocks):
self.assertIsInstance(block, Heading)
self.assertEqual(block.level, expected_levels[i])
words = list(block.words())
self.assertEqual(len(words), 2) # "Heading" and number
self.assertEqual(words[0][1].text, "Heading")
def test_blockquote(self):
"""Test blockquote elements."""
text = "<blockquote><p>This is a quoted paragraph.</p></blockquote>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
self.assertIsInstance(blocks[0], Quote)
# Check that the quote contains a paragraph
quote_blocks = list(blocks[0].blocks())
self.assertEqual(len(quote_blocks), 1)
self.assertIsInstance(quote_blocks[0], Paragraph)
def test_preformatted_code(self):
"""Test preformatted code blocks."""
text = "<pre><code>function hello() {\n console.log('Hello');\n}</code></pre>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
self.assertIsInstance(blocks[0], CodeBlock)
lines = list(blocks[0].lines())
self.assertGreater(len(lines), 0)
def test_unordered_list(self):
"""Test unordered lists."""
text = "<ul><li>First item</li><li>Second item</li><li>Third item</li></ul>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
self.assertIsInstance(blocks[0], HList)
self.assertEqual(blocks[0].style, ListStyle.UNORDERED)
items = list(blocks[0].items())
self.assertEqual(len(items), 3)
def test_ordered_list(self):
"""Test ordered lists."""
text = "<ol><li>First item</li><li>Second item</li><li>Third item</li></ol>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
self.assertIsInstance(blocks[0], HList)
self.assertEqual(blocks[0].style, ListStyle.ORDERED)
def test_list_with_styled_content(self):
"""Test lists containing styled content."""
text = "<ul><li>Normal item</li><li><strong>Bold item</strong></li><li>Item with <em>italic</em> text</li></ul>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
self.assertIsInstance(blocks[0], HList)
items = list(blocks[0].items())
self.assertEqual(len(items), 3)
# Check second item has bold text
second_item_blocks = list(items[1].blocks())
if second_item_blocks:
words = list(second_item_blocks[0].words())
bold_words = [w for _, w in words if w.style.weight == FontWeight.BOLD]
self.assertGreater(len(bold_words), 0)
def test_table_basic(self):
"""Test basic table structure."""
text = """
<table>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
<tr>
<td>Cell 1</td>
<td>Cell 2</td>
</tr>
</table>
"""
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
self.assertIsInstance(blocks[0], Table)
def test_semantic_elements(self):
"""Test semantic HTML5 elements treated as containers."""
text = "<section><article><p>Article content</p></article></section>"
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
self.assertIsInstance(blocks[0], Paragraph)
def test_nested_block_elements(self):
"""Test nested block elements."""
text = """
<div>
<h2>Section Title</h2>
<p>Some introductory text.</p>
<blockquote>
<p>A quoted paragraph.</p>
</blockquote>
</div>
"""
blocks = parse_html_string(text)
self.assertGreater(len(blocks), 2)
# Should have at least a heading, paragraph, and quote
has_heading = any(isinstance(b, Heading) for b in blocks)
has_paragraph = any(isinstance(b, Paragraph) for b in blocks)
has_quote = any(isinstance(b, Quote) for b in blocks)
self.assertTrue(has_heading, "Should contain a heading")
self.assertTrue(has_paragraph, "Should contain a paragraph")
self.assertTrue(has_quote, "Should contain a quote")
def test_empty_elements(self):
"""Test handling of empty elements."""
text = "<p></p><div></div><span></span>"
blocks = parse_html_string(text)
# Empty elements may not create blocks, which is acceptable behavior
self.assertGreaterEqual(len(blocks), 0)
# Test that empty paragraph with some content does create a block
text_with_content = "<p> </p>" # Contains whitespace
blocks_with_content = parse_html_string(text_with_content)
# This should create at least one block since there's whitespace content
self.assertGreaterEqual(len(blocks_with_content), 0)
class TestHTMLComplexStructures(unittest.TestCase):
"""Test cases for complex HTML structures combining multiple features."""
def test_article_with_mixed_content(self):
"""Test a realistic article structure with mixed content."""
text = """
<article>
<h1>Article Title</h1>
<p>This is the <strong>introduction</strong> paragraph with <em>some emphasis</em>.</p>
<blockquote>
<p>This is a <span style="color: blue;">quoted section</span> with styling.</p>
</blockquote>
<ul>
<li>First <strong>important</strong> point</li>
<li>Second point with <code>inline code</code></li>
</ul>
</article>
"""
blocks = parse_html_string(text)
self.assertGreater(len(blocks), 3)
# Verify we have the expected block types
block_types = [type(b).__name__ for b in blocks]
self.assertIn('Heading', block_types)
self.assertIn('Paragraph', block_types)
self.assertIn('Quote', block_types)
self.assertIn('HList', block_types)
def test_styled_table_content(self):
"""Test table with styled cell content."""
text = """
<table>
<thead>
<tr>
<th><strong>Product</strong></th>
<th><em>Price</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>Item with <span style="color: red;">red text</span></td>
<td><strong>$19.99</strong></td>
</tr>
</tbody>
</table>
"""
blocks = parse_html_string(text)
self.assertEqual(len(blocks), 1)
self.assertIsInstance(blocks[0], Table)
if __name__ == '__main__':
unittest.main()
+2 -2
View File
@@ -8,7 +8,7 @@ import unittest
from unittest.mock import Mock, MagicMock
from pyWebLayout.io.readers.html_text import HTMLTextProcessor
from pyWebLayout.io.readers.html_style import HTMLStyleManager
from pyWebLayout.abstract.block import Parapgraph
from pyWebLayout.abstract.block import Paragraph
from pyWebLayout.abstract.inline import Word
@@ -21,7 +21,7 @@ class TestHTMLTextProcessor(unittest.TestCase):
self.text_processor = HTMLTextProcessor(self.style_manager)
# Create a mock paragraph
self.mock_paragraph = Mock(spec=Parapgraph)
self.mock_paragraph = Mock(spec=Paragraph)
self.mock_paragraph.add_word = Mock()
def test_initialization(self):