This commit is contained in:
parent
4029fdff96
commit
da02d48555
@ -1,5 +1,5 @@
|
||||
<svg width="140" height="20" viewBox="0 0 140 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<title>interrogate: 90.0%</title>
|
||||
<title>interrogate: 90.1%</title>
|
||||
<g transform="matrix(1,0,0,1,22,0)">
|
||||
<g id="backgrounds" transform="matrix(1.32789,0,0,1,-22.3892,0)">
|
||||
<rect x="0" y="0" width="71" height="20" style="fill:rgb(85,85,85);"/>
|
||||
@ -12,8 +12,8 @@
|
||||
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
|
||||
<text x="590" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="610">interrogate</text>
|
||||
<text x="590" y="140" transform="scale(.1)" textLength="610">interrogate</text>
|
||||
<text x="1160" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370" data-interrogate="result">90.0%</text>
|
||||
<text x="1160" y="140" transform="scale(.1)" textLength="370" data-interrogate="result">90.0%</text>
|
||||
<text x="1160" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370" data-interrogate="result">90.1%</text>
|
||||
<text x="1160" y="140" transform="scale(.1)" textLength="370" data-interrogate="result">90.1%</text>
|
||||
</g>
|
||||
<g id="logo-shadow" serif:id="logo shadow" transform="matrix(0.854876,0,0,0.854876,-6.73514,1.732)">
|
||||
<g transform="matrix(0.299012,0,0,0.299012,9.70229,-6.68582)">
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@ -1 +1 @@
|
||||
33.9%
|
||||
35.0%
|
||||
File diff suppressed because one or more lines are too long
@ -15,7 +15,7 @@
|
||||
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
||||
<text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
|
||||
<text x="31.5" y="14">coverage</text>
|
||||
<text x="80" y="15" fill="#010101" fill-opacity=".3">34%</text>
|
||||
<text x="80" y="14">34%</text>
|
||||
<text x="80" y="15" fill="#010101" fill-opacity=".3">35%</text>
|
||||
<text x="80" y="14">35%</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 904 B After Width: | Height: | Size: 904 B |
677
coverage.xml
677
coverage.xml
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" ?>
|
||||
<coverage version="7.8.2" timestamp="1749312034554" lines-valid="3794" lines-covered="1519" line-rate="0.4004" branches-valid="1364" branches-covered="227" branch-rate="0.1664" complexity="0">
|
||||
<coverage version="7.8.2" timestamp="1749313463565" lines-valid="3863" lines-covered="1591" line-rate="0.4119" branches-valid="1384" branches-covered="247" branch-rate="0.1785" complexity="0">
|
||||
<!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.8.2 -->
|
||||
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
|
||||
<sources>
|
||||
@ -652,7 +652,7 @@
|
||||
</class>
|
||||
</classes>
|
||||
</package>
|
||||
<package name="abstract" line-rate="0.8326" branch-rate="0.5756" complexity="0">
|
||||
<package name="abstract" line-rate="0.8352" branch-rate="0.6042" complexity="0">
|
||||
<classes>
|
||||
<class name="__init__.py" filename="abstract/__init__.py" complexity="0" line-rate="1" branch-rate="1">
|
||||
<methods/>
|
||||
@ -665,18 +665,18 @@
|
||||
<line number="6" hits="1"/>
|
||||
</lines>
|
||||
</class>
|
||||
<class name="block.py" filename="abstract/block.py" complexity="0" line-rate="0.7248" branch-rate="0.3333">
|
||||
<class name="block.py" filename="abstract/block.py" complexity="0" line-rate="0.7442" branch-rate="0.4273">
|
||||
<methods/>
|
||||
<lines>
|
||||
<line number="1" hits="1"/>
|
||||
<line number="2" hits="1"/>
|
||||
<line number="3" hits="1"/>
|
||||
<line number="4" hits="1"/>
|
||||
<line number="5" hits="1"/>
|
||||
<line number="6" hits="1"/>
|
||||
<line number="7" hits="1"/>
|
||||
<line number="8" hits="1"/>
|
||||
<line number="9" hits="1"/>
|
||||
<line number="10" hits="1"/>
|
||||
<line number="11" hits="1"/>
|
||||
<line number="12" hits="1"/>
|
||||
<line number="13" hits="1"/>
|
||||
<line number="14" hits="1"/>
|
||||
<line number="15" hits="1"/>
|
||||
@ -684,12 +684,14 @@
|
||||
<line number="17" hits="1"/>
|
||||
<line number="18" hits="1"/>
|
||||
<line number="19" hits="1"/>
|
||||
<line number="20" hits="1"/>
|
||||
<line number="21" hits="1"/>
|
||||
<line number="22" hits="1"/>
|
||||
<line number="28" hits="1"/>
|
||||
<line number="35" hits="1"/>
|
||||
<line number="36" hits="1"/>
|
||||
<line number="38" hits="1"/>
|
||||
<line number="39" hits="1"/>
|
||||
<line number="23" hits="1"/>
|
||||
<line number="24" hits="1"/>
|
||||
<line number="27" hits="1"/>
|
||||
<line number="33" hits="1"/>
|
||||
<line number="40" hits="1"/>
|
||||
<line number="41" hits="1"/>
|
||||
<line number="43" hits="1"/>
|
||||
<line number="44" hits="1"/>
|
||||
@ -697,253 +699,253 @@
|
||||
<line number="48" hits="1"/>
|
||||
<line number="49" hits="1"/>
|
||||
<line number="51" hits="1"/>
|
||||
<line number="53" hits="1"/>
|
||||
<line number="54" hits="1"/>
|
||||
<line number="56" hits="1"/>
|
||||
<line number="59" hits="1"/>
|
||||
<line number="66" hits="1"/>
|
||||
<line number="67" hits="1"/>
|
||||
<line number="68" hits="1"/>
|
||||
<line number="69" hits="1"/>
|
||||
<line number="64" hits="1"/>
|
||||
<line number="71" hits="1"/>
|
||||
<line number="72" hits="1"/>
|
||||
<line number="88" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="89,90"/>
|
||||
<line number="89" hits="0"/>
|
||||
<line number="90" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="91,94"/>
|
||||
<line number="91" hits="0"/>
|
||||
<line number="73" hits="1"/>
|
||||
<line number="74" hits="1"/>
|
||||
<line number="76" hits="1"/>
|
||||
<line number="77" hits="1"/>
|
||||
<line number="93" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="94,95"/>
|
||||
<line number="94" hits="0"/>
|
||||
<line number="97" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="98,100"/>
|
||||
<line number="98" hits="0"/>
|
||||
<line number="100" hits="0"/>
|
||||
<line number="102" hits="0"/>
|
||||
<line number="104" hits="1"/>
|
||||
<line number="105" hits="1"/>
|
||||
<line number="95" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="96,99"/>
|
||||
<line number="96" hits="0"/>
|
||||
<line number="99" hits="0"/>
|
||||
<line number="102" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="103,105"/>
|
||||
<line number="103" hits="0"/>
|
||||
<line number="105" hits="0"/>
|
||||
<line number="107" hits="0"/>
|
||||
<line number="109" hits="1"/>
|
||||
<line number="110" hits="1"/>
|
||||
<line number="112" hits="0"/>
|
||||
<line number="114" hits="1"/>
|
||||
<line number="121" hits="1"/>
|
||||
<line number="123" hits="1"/>
|
||||
<line number="138" hits="0"/>
|
||||
<line number="140" hits="1"/>
|
||||
<line number="147" hits="0"/>
|
||||
<line number="149" hits="1"/>
|
||||
<line number="160" hits="0"/>
|
||||
<line number="162" hits="1"/>
|
||||
<line number="169" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="170" hits="1"/>
|
||||
<line number="172" hits="1"/>
|
||||
<line number="179" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="exit,180"/>
|
||||
<line number="180" hits="0"/>
|
||||
<line number="182" hits="1"/>
|
||||
<line number="183" hits="1"/>
|
||||
<line number="185" hits="1"/>
|
||||
<line number="115" hits="1"/>
|
||||
<line number="117" hits="0"/>
|
||||
<line number="119" hits="1"/>
|
||||
<line number="126" hits="1"/>
|
||||
<line number="128" hits="1"/>
|
||||
<line number="143" hits="0"/>
|
||||
<line number="145" hits="1"/>
|
||||
<line number="152" hits="0"/>
|
||||
<line number="154" hits="1"/>
|
||||
<line number="165" hits="0"/>
|
||||
<line number="167" hits="1"/>
|
||||
<line number="174" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="175" hits="1"/>
|
||||
<line number="177" hits="1"/>
|
||||
<line number="184" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="exit,185"/>
|
||||
<line number="185" hits="0"/>
|
||||
<line number="187" hits="1"/>
|
||||
<line number="189" hits="1"/>
|
||||
<line number="188" hits="1"/>
|
||||
<line number="190" hits="1"/>
|
||||
<line number="192" hits="1"/>
|
||||
<line number="194" hits="1"/>
|
||||
<line number="195" hits="1"/>
|
||||
<line number="196" hits="1"/>
|
||||
<line number="197" hits="1"/>
|
||||
<line number="198" hits="1"/>
|
||||
<line number="199" hits="1"/>
|
||||
<line number="200" hits="1"/>
|
||||
<line number="201" hits="1"/>
|
||||
<line number="202" hits="1"/>
|
||||
<line number="208" hits="1"/>
|
||||
<line number="216" hits="1"/>
|
||||
<line number="217" hits="1"/>
|
||||
<line number="218" hits="1"/>
|
||||
<line number="220" hits="1"/>
|
||||
<line number="203" hits="1"/>
|
||||
<line number="204" hits="1"/>
|
||||
<line number="207" hits="1"/>
|
||||
<line number="213" hits="1"/>
|
||||
<line number="221" hits="1"/>
|
||||
<line number="238" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="239,240"/>
|
||||
<line number="239" hits="0"/>
|
||||
<line number="240" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="241,244"/>
|
||||
<line number="241" hits="0"/>
|
||||
<line number="222" hits="1"/>
|
||||
<line number="223" hits="1"/>
|
||||
<line number="225" hits="1"/>
|
||||
<line number="226" hits="1"/>
|
||||
<line number="243" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="244,245"/>
|
||||
<line number="244" hits="0"/>
|
||||
<line number="247" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="248,250"/>
|
||||
<line number="248" hits="0"/>
|
||||
<line number="250" hits="0"/>
|
||||
<line number="252" hits="0"/>
|
||||
<line number="254" hits="1"/>
|
||||
<line number="255" hits="1"/>
|
||||
<line number="257" hits="1"/>
|
||||
<line number="245" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="246,249"/>
|
||||
<line number="246" hits="0"/>
|
||||
<line number="249" hits="0"/>
|
||||
<line number="252" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="253,255"/>
|
||||
<line number="253" hits="0"/>
|
||||
<line number="255" hits="0"/>
|
||||
<line number="257" hits="0"/>
|
||||
<line number="259" hits="1"/>
|
||||
<line number="260" hits="1"/>
|
||||
<line number="262" hits="1"/>
|
||||
<line number="264" hits="1"/>
|
||||
<line number="265" hits="1"/>
|
||||
<line number="267" hits="1"/>
|
||||
<line number="270" hits="1"/>
|
||||
<line number="277" hits="1"/>
|
||||
<line number="278" hits="1"/>
|
||||
<line number="279" hits="1"/>
|
||||
<line number="281" hits="1"/>
|
||||
<line number="275" hits="1"/>
|
||||
<line number="282" hits="1"/>
|
||||
<line number="298" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="299,300"/>
|
||||
<line number="299" hits="0"/>
|
||||
<line number="300" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="301,304"/>
|
||||
<line number="301" hits="0"/>
|
||||
<line number="283" hits="1"/>
|
||||
<line number="284" hits="1"/>
|
||||
<line number="286" hits="1"/>
|
||||
<line number="287" hits="1"/>
|
||||
<line number="303" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="304,305"/>
|
||||
<line number="304" hits="0"/>
|
||||
<line number="307" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="308,310"/>
|
||||
<line number="308" hits="0"/>
|
||||
<line number="310" hits="0"/>
|
||||
<line number="312" hits="0"/>
|
||||
<line number="314" hits="1"/>
|
||||
<line number="315" hits="1"/>
|
||||
<line number="305" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="306,309"/>
|
||||
<line number="306" hits="0"/>
|
||||
<line number="309" hits="0"/>
|
||||
<line number="312" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="313,315"/>
|
||||
<line number="313" hits="0"/>
|
||||
<line number="315" hits="0"/>
|
||||
<line number="317" hits="0"/>
|
||||
<line number="319" hits="1"/>
|
||||
<line number="320" hits="1"/>
|
||||
<line number="322" hits="0"/>
|
||||
<line number="324" hits="1"/>
|
||||
<line number="331" hits="1"/>
|
||||
<line number="332" hits="1"/>
|
||||
<line number="334" hits="1"/>
|
||||
<line number="344" hits="0"/>
|
||||
<line number="346" hits="1"/>
|
||||
<line number="357" hits="0"/>
|
||||
<line number="359" hits="1"/>
|
||||
<line number="366" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="367" hits="1"/>
|
||||
<line number="370" hits="1"/>
|
||||
<line number="325" hits="1"/>
|
||||
<line number="327" hits="0"/>
|
||||
<line number="329" hits="1"/>
|
||||
<line number="336" hits="1"/>
|
||||
<line number="337" hits="1"/>
|
||||
<line number="339" hits="1"/>
|
||||
<line number="349" hits="0"/>
|
||||
<line number="351" hits="1"/>
|
||||
<line number="362" hits="0"/>
|
||||
<line number="364" hits="1"/>
|
||||
<line number="371" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="372" hits="1"/>
|
||||
<line number="375" hits="1"/>
|
||||
<line number="382" hits="1"/>
|
||||
<line number="383" hits="1"/>
|
||||
<line number="384" hits="1"/>
|
||||
<line number="386" hits="1"/>
|
||||
<line number="380" hits="1"/>
|
||||
<line number="387" hits="1"/>
|
||||
<line number="402" hits="0"/>
|
||||
<line number="405" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="406,408"/>
|
||||
<line number="406" hits="0"/>
|
||||
<line number="408" hits="0"/>
|
||||
<line number="410" hits="0"/>
|
||||
<line number="412" hits="1"/>
|
||||
<line number="413" hits="1"/>
|
||||
<line number="415" hits="1"/>
|
||||
<line number="388" hits="1"/>
|
||||
<line number="389" hits="1"/>
|
||||
<line number="391" hits="1"/>
|
||||
<line number="392" hits="1"/>
|
||||
<line number="407" hits="0"/>
|
||||
<line number="410" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="411,413"/>
|
||||
<line number="411" hits="0"/>
|
||||
<line number="413" hits="0"/>
|
||||
<line number="415" hits="0"/>
|
||||
<line number="417" hits="1"/>
|
||||
<line number="418" hits="1"/>
|
||||
<line number="420" hits="1"/>
|
||||
<line number="422" hits="1"/>
|
||||
<line number="429" hits="1"/>
|
||||
<line number="431" hits="1"/>
|
||||
<line number="438" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="439" hits="1"/>
|
||||
<line number="441" hits="1"/>
|
||||
<line number="442" hits="1"/>
|
||||
<line number="423" hits="1"/>
|
||||
<line number="425" hits="1"/>
|
||||
<line number="427" hits="1"/>
|
||||
<line number="434" hits="1"/>
|
||||
<line number="436" hits="1"/>
|
||||
<line number="443" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="444" hits="1"/>
|
||||
<line number="446" hits="1"/>
|
||||
<line number="447" hits="1"/>
|
||||
<line number="449" hits="1"/>
|
||||
<line number="450" hits="1"/>
|
||||
<line number="451" hits="1"/>
|
||||
<line number="452" hits="1"/>
|
||||
<line number="454" hits="1"/>
|
||||
<line number="455" hits="1"/>
|
||||
<line number="456" hits="1"/>
|
||||
<line number="459" hits="1"/>
|
||||
<line number="467" hits="1"/>
|
||||
<line number="468" hits="1"/>
|
||||
<line number="469" hits="1"/>
|
||||
<line number="470" hits="1"/>
|
||||
<line number="464" hits="1"/>
|
||||
<line number="472" hits="1"/>
|
||||
<line number="473" hits="1"/>
|
||||
<line number="490" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="491,492"/>
|
||||
<line number="491" hits="0"/>
|
||||
<line number="492" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="493,496"/>
|
||||
<line number="493" hits="0"/>
|
||||
<line number="474" hits="1"/>
|
||||
<line number="475" hits="1"/>
|
||||
<line number="477" hits="1"/>
|
||||
<line number="478" hits="1"/>
|
||||
<line number="495" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="496,497"/>
|
||||
<line number="496" hits="0"/>
|
||||
<line number="499" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="500,502"/>
|
||||
<line number="500" hits="0"/>
|
||||
<line number="502" hits="0"/>
|
||||
<line number="504" hits="0"/>
|
||||
<line number="506" hits="1"/>
|
||||
<line number="507" hits="1"/>
|
||||
<line number="509" hits="1"/>
|
||||
<line number="497" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="498,501"/>
|
||||
<line number="498" hits="0"/>
|
||||
<line number="501" hits="0"/>
|
||||
<line number="504" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="505,507"/>
|
||||
<line number="505" hits="0"/>
|
||||
<line number="507" hits="0"/>
|
||||
<line number="509" hits="0"/>
|
||||
<line number="511" hits="1"/>
|
||||
<line number="512" hits="1"/>
|
||||
<line number="514" hits="1"/>
|
||||
<line number="516" hits="1"/>
|
||||
<line number="517" hits="1"/>
|
||||
<line number="519" hits="0"/>
|
||||
<line number="519" hits="1"/>
|
||||
<line number="521" hits="1"/>
|
||||
<line number="522" hits="1"/>
|
||||
<line number="524" hits="0"/>
|
||||
<line number="526" hits="1"/>
|
||||
<line number="533" hits="1"/>
|
||||
<line number="534" hits="1"/>
|
||||
<line number="536" hits="1"/>
|
||||
<line number="547" hits="0"/>
|
||||
<line number="549" hits="1"/>
|
||||
<line number="556" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="557" hits="1"/>
|
||||
<line number="559" hits="1"/>
|
||||
<line number="560" hits="1"/>
|
||||
<line number="527" hits="1"/>
|
||||
<line number="529" hits="0"/>
|
||||
<line number="531" hits="1"/>
|
||||
<line number="538" hits="1"/>
|
||||
<line number="539" hits="1"/>
|
||||
<line number="541" hits="1"/>
|
||||
<line number="552" hits="0"/>
|
||||
<line number="554" hits="1"/>
|
||||
<line number="561" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="562" hits="1"/>
|
||||
<line number="564" hits="1"/>
|
||||
<line number="565" hits="1"/>
|
||||
<line number="567" hits="1"/>
|
||||
<line number="570" hits="1"/>
|
||||
<line number="578" hits="1"/>
|
||||
<line number="579" hits="1"/>
|
||||
<line number="580" hits="1"/>
|
||||
<line number="581" hits="1"/>
|
||||
<line number="575" hits="1"/>
|
||||
<line number="583" hits="1"/>
|
||||
<line number="584" hits="1"/>
|
||||
<line number="601" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="602,603"/>
|
||||
<line number="602" hits="0"/>
|
||||
<line number="603" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="604,607"/>
|
||||
<line number="604" hits="0"/>
|
||||
<line number="585" hits="1"/>
|
||||
<line number="586" hits="1"/>
|
||||
<line number="588" hits="1"/>
|
||||
<line number="589" hits="1"/>
|
||||
<line number="606" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="607,608"/>
|
||||
<line number="607" hits="0"/>
|
||||
<line number="610" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="611,613"/>
|
||||
<line number="611" hits="0"/>
|
||||
<line number="613" hits="0"/>
|
||||
<line number="615" hits="0"/>
|
||||
<line number="617" hits="1"/>
|
||||
<line number="618" hits="1"/>
|
||||
<line number="620" hits="1"/>
|
||||
<line number="608" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="609,612"/>
|
||||
<line number="609" hits="0"/>
|
||||
<line number="612" hits="0"/>
|
||||
<line number="615" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="616,618"/>
|
||||
<line number="616" hits="0"/>
|
||||
<line number="618" hits="0"/>
|
||||
<line number="620" hits="0"/>
|
||||
<line number="622" hits="1"/>
|
||||
<line number="623" hits="1"/>
|
||||
<line number="625" hits="1"/>
|
||||
<line number="627" hits="1"/>
|
||||
<line number="628" hits="1"/>
|
||||
<line number="630" hits="0"/>
|
||||
<line number="630" hits="1"/>
|
||||
<line number="632" hits="1"/>
|
||||
<line number="633" hits="1"/>
|
||||
<line number="635" hits="0"/>
|
||||
<line number="637" hits="1"/>
|
||||
<line number="644" hits="1"/>
|
||||
<line number="645" hits="1"/>
|
||||
<line number="647" hits="1"/>
|
||||
<line number="657" hits="0"/>
|
||||
<line number="659" hits="1"/>
|
||||
<line number="670" hits="0"/>
|
||||
<line number="672" hits="1"/>
|
||||
<line number="679" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="680"/>
|
||||
<line number="680" hits="0"/>
|
||||
<line number="683" hits="1"/>
|
||||
<line number="638" hits="1"/>
|
||||
<line number="640" hits="0"/>
|
||||
<line number="642" hits="1"/>
|
||||
<line number="649" hits="1"/>
|
||||
<line number="650" hits="1"/>
|
||||
<line number="652" hits="1"/>
|
||||
<line number="662" hits="0"/>
|
||||
<line number="664" hits="1"/>
|
||||
<line number="675" hits="0"/>
|
||||
<line number="677" hits="1"/>
|
||||
<line number="684" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="685" hits="1"/>
|
||||
<line number="688" hits="1"/>
|
||||
<line number="698" hits="1"/>
|
||||
<line number="699" hits="1"/>
|
||||
<line number="700" hits="1"/>
|
||||
<line number="701" hits="1"/>
|
||||
<line number="702" hits="1"/>
|
||||
<line number="693" hits="1"/>
|
||||
<line number="703" hits="1"/>
|
||||
<line number="704" hits="1"/>
|
||||
<line number="705" hits="1"/>
|
||||
<line number="706" hits="1"/>
|
||||
<line number="726" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="727,730"/>
|
||||
<line number="727" hits="0"/>
|
||||
<line number="730" hits="0"/>
|
||||
<line number="733" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="734,736"/>
|
||||
<line number="734" hits="0"/>
|
||||
<line number="736" hits="0"/>
|
||||
<line number="738" hits="0"/>
|
||||
<line number="740" hits="1"/>
|
||||
<line number="741" hits="1"/>
|
||||
<line number="743" hits="1"/>
|
||||
<line number="707" hits="1"/>
|
||||
<line number="708" hits="1"/>
|
||||
<line number="710" hits="1"/>
|
||||
<line number="711" hits="1"/>
|
||||
<line number="731" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="732,735"/>
|
||||
<line number="732" hits="0"/>
|
||||
<line number="735" hits="0"/>
|
||||
<line number="738" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="739,741"/>
|
||||
<line number="739" hits="0"/>
|
||||
<line number="741" hits="0"/>
|
||||
<line number="743" hits="0"/>
|
||||
<line number="745" hits="1"/>
|
||||
<line number="746" hits="1"/>
|
||||
<line number="748" hits="0"/>
|
||||
<line number="748" hits="1"/>
|
||||
<line number="750" hits="1"/>
|
||||
<line number="751" hits="1"/>
|
||||
<line number="753" hits="1"/>
|
||||
<line number="753" hits="0"/>
|
||||
<line number="755" hits="1"/>
|
||||
<line number="756" hits="1"/>
|
||||
<line number="758" hits="0"/>
|
||||
<line number="758" hits="1"/>
|
||||
<line number="760" hits="1"/>
|
||||
<line number="761" hits="1"/>
|
||||
<line number="763" hits="1"/>
|
||||
<line number="763" hits="0"/>
|
||||
<line number="765" hits="1"/>
|
||||
<line number="766" hits="1"/>
|
||||
<line number="768" hits="0"/>
|
||||
<line number="768" hits="1"/>
|
||||
<line number="770" hits="1"/>
|
||||
<line number="771" hits="1"/>
|
||||
<line number="773" hits="0"/>
|
||||
@ -951,71 +953,71 @@
|
||||
<line number="776" hits="1"/>
|
||||
<line number="778" hits="0"/>
|
||||
<line number="780" hits="1"/>
|
||||
<line number="787" hits="1"/>
|
||||
<line number="788" hits="1"/>
|
||||
<line number="790" hits="1"/>
|
||||
<line number="800" hits="0"/>
|
||||
<line number="802" hits="1"/>
|
||||
<line number="813" hits="0"/>
|
||||
<line number="815" hits="1"/>
|
||||
<line number="822" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="exit,823"/>
|
||||
<line number="823" hits="0"/>
|
||||
<line number="826" hits="1"/>
|
||||
<line number="781" hits="1"/>
|
||||
<line number="783" hits="0"/>
|
||||
<line number="785" hits="1"/>
|
||||
<line number="792" hits="1"/>
|
||||
<line number="793" hits="1"/>
|
||||
<line number="795" hits="1"/>
|
||||
<line number="805" hits="0"/>
|
||||
<line number="807" hits="1"/>
|
||||
<line number="818" hits="0"/>
|
||||
<line number="820" hits="1"/>
|
||||
<line number="827" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="exit,828"/>
|
||||
<line number="828" hits="0"/>
|
||||
<line number="831" hits="1"/>
|
||||
<line number="838" hits="1"/>
|
||||
<line number="839" hits="1"/>
|
||||
<line number="840" hits="1"/>
|
||||
<line number="842" hits="1"/>
|
||||
<line number="836" hits="1"/>
|
||||
<line number="843" hits="1"/>
|
||||
<line number="860" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="861,864"/>
|
||||
<line number="861" hits="0"/>
|
||||
<line number="864" hits="0"/>
|
||||
<line number="867" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="868,870"/>
|
||||
<line number="868" hits="0"/>
|
||||
<line number="870" hits="0"/>
|
||||
<line number="872" hits="0"/>
|
||||
<line number="874" hits="1"/>
|
||||
<line number="875" hits="1"/>
|
||||
<line number="844" hits="1"/>
|
||||
<line number="845" hits="1"/>
|
||||
<line number="847" hits="1"/>
|
||||
<line number="848" hits="1"/>
|
||||
<line number="865" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="866,869"/>
|
||||
<line number="866" hits="0"/>
|
||||
<line number="869" hits="0"/>
|
||||
<line number="872" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="873,875"/>
|
||||
<line number="873" hits="0"/>
|
||||
<line number="875" hits="0"/>
|
||||
<line number="877" hits="0"/>
|
||||
<line number="879" hits="1"/>
|
||||
<line number="880" hits="1"/>
|
||||
<line number="882" hits="0"/>
|
||||
<line number="884" hits="1"/>
|
||||
<line number="891" hits="1"/>
|
||||
<line number="892" hits="1"/>
|
||||
<line number="894" hits="1"/>
|
||||
<line number="907" hits="0"/>
|
||||
<line number="909" hits="1"/>
|
||||
<line number="916" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="exit,917"/>
|
||||
<line number="917" hits="0"/>
|
||||
<line number="919" hits="1"/>
|
||||
<line number="920" hits="1"/>
|
||||
<line number="922" hits="1"/>
|
||||
<line number="885" hits="1"/>
|
||||
<line number="887" hits="0"/>
|
||||
<line number="889" hits="1"/>
|
||||
<line number="896" hits="1"/>
|
||||
<line number="897" hits="1"/>
|
||||
<line number="899" hits="1"/>
|
||||
<line number="912" hits="0"/>
|
||||
<line number="914" hits="1"/>
|
||||
<line number="921" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="exit,922"/>
|
||||
<line number="922" hits="0"/>
|
||||
<line number="924" hits="1"/>
|
||||
<line number="925" hits="1"/>
|
||||
<line number="927" hits="1"/>
|
||||
<line number="930" hits="1"/>
|
||||
<line number="938" hits="1"/>
|
||||
<line number="939" hits="1"/>
|
||||
<line number="940" hits="1"/>
|
||||
<line number="941" hits="1"/>
|
||||
<line number="942" hits="1"/>
|
||||
<line number="935" hits="1"/>
|
||||
<line number="943" hits="1"/>
|
||||
<line number="944" hits="1"/>
|
||||
<line number="945" hits="1"/>
|
||||
<line number="946" hits="1"/>
|
||||
<line number="963" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="964,965"/>
|
||||
<line number="964" hits="0"/>
|
||||
<line number="965" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="966,969"/>
|
||||
<line number="966" hits="0"/>
|
||||
<line number="947" hits="1"/>
|
||||
<line number="948" hits="1"/>
|
||||
<line number="950" hits="1"/>
|
||||
<line number="951" hits="1"/>
|
||||
<line number="968" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="969,970"/>
|
||||
<line number="969" hits="0"/>
|
||||
<line number="972" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="973,975"/>
|
||||
<line number="973" hits="0"/>
|
||||
<line number="975" hits="0"/>
|
||||
<line number="977" hits="0"/>
|
||||
<line number="979" hits="1"/>
|
||||
<line number="980" hits="1"/>
|
||||
<line number="982" hits="1"/>
|
||||
<line number="970" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="971,974"/>
|
||||
<line number="971" hits="0"/>
|
||||
<line number="974" hits="0"/>
|
||||
<line number="977" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="978,980"/>
|
||||
<line number="978" hits="0"/>
|
||||
<line number="980" hits="0"/>
|
||||
<line number="982" hits="0"/>
|
||||
<line number="984" hits="1"/>
|
||||
<line number="985" hits="1"/>
|
||||
<line number="987" hits="0"/>
|
||||
<line number="987" hits="1"/>
|
||||
<line number="989" hits="1"/>
|
||||
<line number="990" hits="1"/>
|
||||
<line number="992" hits="0"/>
|
||||
@ -1023,50 +1025,50 @@
|
||||
<line number="995" hits="1"/>
|
||||
<line number="997" hits="0"/>
|
||||
<line number="999" hits="1"/>
|
||||
<line number="1007" hits="1"/>
|
||||
<line number="1009" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1010" hits="1"/>
|
||||
<line number="1011" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1000" hits="1"/>
|
||||
<line number="1002" hits="0"/>
|
||||
<line number="1004" hits="1"/>
|
||||
<line number="1012" hits="1"/>
|
||||
<line number="1014" hits="1"/>
|
||||
<line number="1016" hits="1"/>
|
||||
<line number="1027" hits="0"/>
|
||||
<line number="1029" hits="1"/>
|
||||
<line number="1036" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1037" hits="1"/>
|
||||
<line number="1039" hits="1"/>
|
||||
<line number="1046" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1047" hits="1"/>
|
||||
<line number="1049" hits="1"/>
|
||||
<line number="1056" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1057" hits="1"/>
|
||||
<line number="1059" hits="1"/>
|
||||
<line number="1066" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1067" hits="1"/>
|
||||
<line number="1068" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1069" hits="1"/>
|
||||
<line number="1070" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1071" hits="1"/>
|
||||
<line number="1073" hits="1"/>
|
||||
<line number="1014" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1015" hits="1"/>
|
||||
<line number="1016" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1017" hits="1"/>
|
||||
<line number="1019" hits="1"/>
|
||||
<line number="1021" hits="1"/>
|
||||
<line number="1032" hits="0"/>
|
||||
<line number="1034" hits="1"/>
|
||||
<line number="1041" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1042" hits="1"/>
|
||||
<line number="1044" hits="1"/>
|
||||
<line number="1051" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1052" hits="1"/>
|
||||
<line number="1054" hits="1"/>
|
||||
<line number="1061" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1062" hits="1"/>
|
||||
<line number="1064" hits="1"/>
|
||||
<line number="1071" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1072" hits="1"/>
|
||||
<line number="1073" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1074" hits="1"/>
|
||||
<line number="1075" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1076" hits="1"/>
|
||||
<line number="1084" hits="1"/>
|
||||
<line number="1078" hits="1"/>
|
||||
<line number="1079" hits="1"/>
|
||||
<line number="1081" hits="1"/>
|
||||
<line number="1089" hits="1"/>
|
||||
<line number="1099" hits="1"/>
|
||||
<line number="1100" hits="1"/>
|
||||
<line number="1101" hits="1"/>
|
||||
<line number="1102" hits="1"/>
|
||||
<line number="1103" hits="1"/>
|
||||
<line number="1094" hits="1"/>
|
||||
<line number="1104" hits="1"/>
|
||||
<line number="1105" hits="1"/>
|
||||
<line number="1106" hits="1"/>
|
||||
<line number="1125" hits="0"/>
|
||||
<line number="1128" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="1129,1131"/>
|
||||
<line number="1129" hits="0"/>
|
||||
<line number="1131" hits="0"/>
|
||||
<line number="1133" hits="0"/>
|
||||
<line number="1135" hits="1"/>
|
||||
<line number="1136" hits="1"/>
|
||||
<line number="1138" hits="1"/>
|
||||
<line number="1107" hits="1"/>
|
||||
<line number="1108" hits="1"/>
|
||||
<line number="1110" hits="1"/>
|
||||
<line number="1111" hits="1"/>
|
||||
<line number="1130" hits="0"/>
|
||||
<line number="1133" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="1134,1136"/>
|
||||
<line number="1134" hits="0"/>
|
||||
<line number="1136" hits="0"/>
|
||||
<line number="1138" hits="0"/>
|
||||
<line number="1140" hits="1"/>
|
||||
<line number="1141" hits="1"/>
|
||||
<line number="1143" hits="1"/>
|
||||
@ -1089,32 +1091,99 @@
|
||||
<line number="1171" hits="1"/>
|
||||
<line number="1173" hits="1"/>
|
||||
<line number="1175" hits="1"/>
|
||||
<line number="1182" hits="1"/>
|
||||
<line number="1184" hits="1"/>
|
||||
<line number="1191" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1192" hits="1"/>
|
||||
<line number="1193" hits="1"/>
|
||||
<line number="1195" hits="1"/>
|
||||
<line number="1207" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1208"/>
|
||||
<line number="1208" hits="0"/>
|
||||
<line number="1210" hits="1"/>
|
||||
<line number="1213" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1217"/>
|
||||
<line number="1214" hits="1"/>
|
||||
<line number="1176" hits="1"/>
|
||||
<line number="1178" hits="1"/>
|
||||
<line number="1180" hits="1"/>
|
||||
<line number="1187" hits="1"/>
|
||||
<line number="1189" hits="1"/>
|
||||
<line number="1196" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1197" hits="1"/>
|
||||
<line number="1198" hits="1"/>
|
||||
<line number="1200" hits="1"/>
|
||||
<line number="1212" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1213"/>
|
||||
<line number="1213" hits="0"/>
|
||||
<line number="1215" hits="1"/>
|
||||
<line number="1217" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1218"/>
|
||||
<line number="1218" hits="0"/>
|
||||
<line number="1219" hits="0"/>
|
||||
<line number="1221" hits="1"/>
|
||||
<line number="1224" hits="1"/>
|
||||
<line number="1229" hits="1"/>
|
||||
<line number="1231" hits="1"/>
|
||||
<line number="1233" hits="1"/>
|
||||
<line number="1234" hits="1"/>
|
||||
<line number="1248" hits="0"/>
|
||||
<line number="1251" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="1252,1254"/>
|
||||
<line number="1252" hits="0"/>
|
||||
<line number="1254" hits="0"/>
|
||||
<line number="1256" hits="0"/>
|
||||
<line number="1218" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1222"/>
|
||||
<line number="1219" hits="1"/>
|
||||
<line number="1220" hits="1"/>
|
||||
<line number="1222" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1223"/>
|
||||
<line number="1223" hits="0"/>
|
||||
<line number="1224" hits="0"/>
|
||||
<line number="1226" hits="1"/>
|
||||
<line number="1228" hits="1"/>
|
||||
<line number="1238" hits="1"/>
|
||||
<line number="1239" hits="1"/>
|
||||
<line number="1241" hits="1"/>
|
||||
<line number="1255" hits="1"/>
|
||||
<line number="1257" hits="1"/>
|
||||
<line number="1259" hits="1"/>
|
||||
<line number="1261" hits="1"/>
|
||||
<line number="1262" hits="1"/>
|
||||
<line number="1264" hits="1"/>
|
||||
<line number="1265" hits="1"/>
|
||||
<line number="1267" hits="1"/>
|
||||
<line number="1268" hits="1"/>
|
||||
<line number="1269" hits="0"/>
|
||||
<line number="1270" hits="0"/>
|
||||
<line number="1271" hits="1"/>
|
||||
<line number="1272" hits="1"/>
|
||||
<line number="1273" hits="0"/>
|
||||
<line number="1274" hits="0"/>
|
||||
<line number="1275" hits="1"/>
|
||||
<line number="1277" hits="1"/>
|
||||
<line number="1288" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1289" hits="1"/>
|
||||
<line number="1291" hits="1"/>
|
||||
<line number="1292" hits="1"/>
|
||||
<line number="1294" hits="1"/>
|
||||
<line number="1295" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1297" hits="1"/>
|
||||
<line number="1298" hits="1"/>
|
||||
<line number="1301" hits="1"/>
|
||||
<line number="1304" hits="1"/>
|
||||
<line number="1306" hits="1"/>
|
||||
<line number="1309" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1310" hits="1"/>
|
||||
<line number="1313" hits="1"/>
|
||||
<line number="1315" hits="1"/>
|
||||
<line number="1317" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1318"/>
|
||||
<line number="1318" hits="0"/>
|
||||
<line number="1319" hits="0"/>
|
||||
<line number="1320" hits="0"/>
|
||||
<line number="1321" hits="0"/>
|
||||
<line number="1322" hits="1"/>
|
||||
<line number="1324" hits="1"/>
|
||||
<line number="1332" hits="1"/>
|
||||
<line number="1334" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1335" hits="1"/>
|
||||
<line number="1338" hits="1"/>
|
||||
<line number="1339" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1361"/>
|
||||
<line number="1341" hits="1"/>
|
||||
<line number="1352" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1357"/>
|
||||
<line number="1353" hits="1"/>
|
||||
<line number="1354" hits="1"/>
|
||||
<line number="1357" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1358" hits="1"/>
|
||||
<line number="1359" hits="1"/>
|
||||
<line number="1361" hits="1"/>
|
||||
<line number="1370" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="1374"/>
|
||||
<line number="1371" hits="1"/>
|
||||
<line number="1374" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="1375" hits="1"/>
|
||||
<line number="1376" hits="1"/>
|
||||
<line number="1377" hits="0"/>
|
||||
<line number="1378" hits="0"/>
|
||||
<line number="1380" hits="1"/>
|
||||
<line number="1383" hits="1"/>
|
||||
<line number="1388" hits="1"/>
|
||||
<line number="1390" hits="1"/>
|
||||
<line number="1392" hits="1"/>
|
||||
<line number="1393" hits="1"/>
|
||||
<line number="1407" hits="0"/>
|
||||
<line number="1410" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="1411,1413"/>
|
||||
<line number="1411" hits="0"/>
|
||||
<line number="1413" hits="0"/>
|
||||
<line number="1415" hits="0"/>
|
||||
</lines>
|
||||
</class>
|
||||
<class name="document.py" filename="abstract/document.py" complexity="0" line-rate="0.8324" branch-rate="0.5938">
|
||||
@ -2356,7 +2425,7 @@
|
||||
</class>
|
||||
</classes>
|
||||
</package>
|
||||
<package name="io.readers" line-rate="0.3445" branch-rate="0.1902" complexity="0">
|
||||
<package name="io.readers" line-rate="0.3542" branch-rate="0.1948" complexity="0">
|
||||
<classes>
|
||||
<class name="__init__.py" filename="io/readers/__init__.py" complexity="0" line-rate="1" branch-rate="1">
|
||||
<methods/>
|
||||
@ -2872,7 +2941,7 @@
|
||||
<line number="186" hits="0"/>
|
||||
</lines>
|
||||
</class>
|
||||
<class name="html_extraction.py" filename="io/readers/html_extraction.py" complexity="0" line-rate="0.796" branch-rate="0.6019">
|
||||
<class name="html_extraction.py" filename="io/readers/html_extraction.py" complexity="0" line-rate="0.83" branch-rate="0.6165">
|
||||
<methods/>
|
||||
<lines>
|
||||
<line number="9" hits="1"/>
|
||||
@ -2907,9 +2976,9 @@
|
||||
<line number="104" hits="1"/>
|
||||
<line number="107" hits="1"/>
|
||||
<line number="110" hits="1"/>
|
||||
<line number="111" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="112"/>
|
||||
<line number="112" hits="0"/>
|
||||
<line number="117" hits="0"/>
|
||||
<line number="111" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="112" hits="1"/>
|
||||
<line number="117" hits="1"/>
|
||||
<line number="118" hits="1"/>
|
||||
<line number="121" hits="1"/>
|
||||
<line number="122" hits="1" branch="true" condition-coverage="100% (2/2)"/>
|
||||
@ -3194,21 +3263,21 @@
|
||||
<line number="619" hits="1"/>
|
||||
<line number="621" hits="1"/>
|
||||
<line number="624" hits="1"/>
|
||||
<line number="626" hits="0"/>
|
||||
<line number="626" hits="1"/>
|
||||
<line number="629" hits="1"/>
|
||||
<line number="632" hits="0"/>
|
||||
<line number="635" hits="1"/>
|
||||
<line number="637" hits="0"/>
|
||||
<line number="638" hits="0"/>
|
||||
<line number="641" hits="0"/>
|
||||
<line number="642" hits="0"/>
|
||||
<line number="643" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="644,645"/>
|
||||
<line number="644" hits="0"/>
|
||||
<line number="645" hits="0" branch="true" condition-coverage="0% (0/2)" missing-branches="646,650"/>
|
||||
<line number="646" hits="0"/>
|
||||
<line number="637" hits="1"/>
|
||||
<line number="638" hits="1"/>
|
||||
<line number="641" hits="1"/>
|
||||
<line number="642" hits="1"/>
|
||||
<line number="643" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="645"/>
|
||||
<line number="644" hits="1"/>
|
||||
<line number="645" hits="1" branch="true" condition-coverage="50% (1/2)" missing-branches="650"/>
|
||||
<line number="646" hits="1"/>
|
||||
<line number="647" hits="0"/>
|
||||
<line number="648" hits="0"/>
|
||||
<line number="650" hits="0"/>
|
||||
<line number="650" hits="1"/>
|
||||
<line number="653" hits="1"/>
|
||||
<line number="655" hits="1"/>
|
||||
<line number="658" hits="1"/>
|
||||
|
||||
@ -34,8 +34,6 @@ from pyWebLayout.concrete.page import Container, Page
|
||||
# Abstract components
|
||||
from pyWebLayout.abstract.inline import Word
|
||||
|
||||
# Layout components
|
||||
from pyWebLayout.table import Table, TableCell
|
||||
|
||||
# IO functionality (reading and writing)
|
||||
from pyWebLayout.io import (
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
from enum import Enum
|
||||
from typing import Callable, Dict, Any, Optional, Union, List, Tuple
|
||||
from pyWebLayout.base import Interactable
|
||||
from pyWebLayout.core.base import Interactable
|
||||
|
||||
|
||||
class LinkType(Enum):
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from __future__ import annotations
|
||||
from pyWebLayout.base import Queriable
|
||||
from pyWebLayout.core.base import Queriable
|
||||
from pyWebLayout.style import Font
|
||||
from typing import Tuple, Union, List, Optional, Dict
|
||||
import pyphen
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
|
||||
from abc import ABC
|
||||
import numpy as np
|
||||
|
||||
from pyWebLayout.style import Alignment
|
||||
|
||||
|
||||
class Renderable(ABC):
|
||||
"""
|
||||
Abstract base class for any object that can be rendered to an image.
|
||||
All renderable objects must implement the render method.
|
||||
"""
|
||||
def render(self):
|
||||
"""
|
||||
Render the object to an image.
|
||||
|
||||
Returns:
|
||||
PIL.Image: The rendered image
|
||||
"""
|
||||
pass
|
||||
|
||||
class Interactable(ABC):
|
||||
"""
|
||||
Abstract base class for any object that can be interacted with.
|
||||
Interactable objects must have a callback that is executed when interacted with.
|
||||
"""
|
||||
def __init__(self, callback=None):
|
||||
"""
|
||||
Initialize an interactable object.
|
||||
|
||||
Args:
|
||||
callback: The function to call when this object is interacted with
|
||||
"""
|
||||
self._callback = callback
|
||||
|
||||
def interact(self, point: np.generic):
|
||||
"""
|
||||
Handle interaction at the given point.
|
||||
|
||||
Args:
|
||||
point: The coordinates of the interaction
|
||||
|
||||
Returns:
|
||||
The result of calling the callback function with the point
|
||||
"""
|
||||
if self._callback is None:
|
||||
return None
|
||||
return self._callback(point)
|
||||
|
||||
class Layoutable(ABC):
|
||||
"""
|
||||
Abstract base class for any object that can be laid out.
|
||||
Layoutable objects must implement the layout method which arranges their contents.
|
||||
"""
|
||||
def layout(self):
|
||||
"""
|
||||
Layout the object's contents.
|
||||
This method should be called before rendering to properly arrange the object's contents.
|
||||
"""
|
||||
pass
|
||||
|
||||
class Queriable(ABC):
|
||||
|
||||
def in_object(self, point:np.generic):
|
||||
"""
|
||||
check if a point is in the object
|
||||
"""
|
||||
pass
|
||||
@ -1,8 +1,8 @@
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from pyWebLayout.base import Renderable, Queriable
|
||||
from pyWebLayout.layout import Alignment
|
||||
from pyWebLayout.core.base import Renderable, Queriable
|
||||
from pyWebLayout.style.layout import Alignment
|
||||
|
||||
class Box(Renderable, Queriable):
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ from typing import Optional, Dict, Any, Tuple, List, Union
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
from pyWebLayout.base import Renderable, Queriable
|
||||
from pyWebLayout.core.base import Renderable, Queriable
|
||||
from pyWebLayout.abstract.functional import Link, Button, Form, FormField, LinkType, FormFieldType
|
||||
from pyWebLayout.style import Font, TextDecoration
|
||||
from .box import Box
|
||||
|
||||
@ -3,10 +3,10 @@ from typing import Optional, Tuple, Union, Dict, Any
|
||||
import numpy as np
|
||||
from PIL import Image as PILImage, ImageDraw, ImageFont
|
||||
|
||||
from pyWebLayout.base import Renderable, Queriable
|
||||
from pyWebLayout.core.base import Renderable, Queriable
|
||||
from pyWebLayout.abstract.block import Image as AbstractImage
|
||||
from .box import Box
|
||||
from pyWebLayout.layout import Alignment
|
||||
from pyWebLayout.style.layout import Alignment
|
||||
|
||||
|
||||
class RenderableImage(Box, Queriable):
|
||||
|
||||
@ -2,9 +2,9 @@ from typing import List, Tuple, Optional, Dict, Any
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from pyWebLayout.base import Renderable, Layoutable
|
||||
from pyWebLayout.core.base import Renderable, Layoutable
|
||||
from .box import Box
|
||||
from pyWebLayout.layout import Alignment
|
||||
from pyWebLayout.style.layout import Alignment
|
||||
|
||||
|
||||
class Container(Box, Layoutable):
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
from pyWebLayout.base import Renderable, Queriable
|
||||
from pyWebLayout.core.base import Renderable, Queriable
|
||||
from .box import Box
|
||||
from pyWebLayout.layout import Alignment
|
||||
from pyWebLayout.style.layout import Alignment
|
||||
from pyWebLayout.style import Font, FontStyle, FontWeight, TextDecoration
|
||||
from pyWebLayout.abstract.inline import Word
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
@ -1,919 +0,0 @@
|
||||
import re
|
||||
from html.parser import HTMLParser as BaseHTMLParser
|
||||
from typing import Dict, List, Optional, Tuple, Union, Any, Set, Callable
|
||||
import urllib.parse
|
||||
from PIL import Image
|
||||
|
||||
from .style import Font, FontStyle, FontWeight, TextDecoration
|
||||
from .abstract.document import Document, MetadataType, Book, Chapter
|
||||
from .abstract.block import (
|
||||
Block, BlockType, Paragraph, Heading, HeadingLevel, Quote, CodeBlock,
|
||||
HList, ListStyle, ListItem, Table, TableRow, TableCell, HorizontalRule
|
||||
)
|
||||
from .abstract.inline import Word, FormattedSpan, LineBreak
|
||||
from .abstract.functional import Link, LinkType, Button, Form, FormField, FormFieldType
|
||||
from .concrete.page import Page
|
||||
from pyWebLayout.layout import Alignment
|
||||
|
||||
|
||||
class HTMLParser(BaseHTMLParser):
|
||||
"""
|
||||
HTML parser that builds an abstract document representation from HTML content.
|
||||
This parser converts HTML to abstract document classes without any rendering specifics.
|
||||
"""
|
||||
|
||||
def __init__(self, base_url: Optional[str] = None):
|
||||
"""
|
||||
Initialize the HTML parser.
|
||||
|
||||
Args:
|
||||
base_url: Base URL for resolving relative links
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
# Document structure
|
||||
self.document = Document()
|
||||
|
||||
# State variables
|
||||
self._current_block = None
|
||||
self._block_stack: List[Block] = []
|
||||
|
||||
# Text handling
|
||||
self._current_paragraph = None
|
||||
self._current_span = None
|
||||
self._text_buffer = ""
|
||||
|
||||
# Style state
|
||||
self._style_stack: List[Dict[str, Any]] = []
|
||||
self._current_style = {
|
||||
'font_size': 12,
|
||||
'font_weight': FontWeight.NORMAL,
|
||||
'font_style': FontStyle.NORMAL,
|
||||
'decoration': TextDecoration.NONE,
|
||||
'color': (0, 0, 0),
|
||||
'background': None,
|
||||
'language': 'en_US'
|
||||
}
|
||||
|
||||
# Tag state
|
||||
self._list_stack: List[HList] = []
|
||||
self._table_stack: List[Table] = []
|
||||
self._current_table_row = None
|
||||
|
||||
# Link handling
|
||||
self._base_url = base_url
|
||||
self._in_link = False
|
||||
self._current_link = None
|
||||
|
||||
# Special state flags
|
||||
self._in_head = False
|
||||
self._in_title = False
|
||||
self._in_script = False
|
||||
self._in_style = False
|
||||
self._script_buffer = ""
|
||||
self._style_buffer = ""
|
||||
self._title_buffer = ""
|
||||
|
||||
def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]):
|
||||
"""
|
||||
Handle the start of an HTML tag.
|
||||
|
||||
Args:
|
||||
tag: The tag name
|
||||
attrs: List of attribute tuples (name, value)
|
||||
"""
|
||||
tag = tag.lower()
|
||||
attrs_dict = dict(attrs)
|
||||
|
||||
# Special handling for elements where we collect content
|
||||
if self._in_script and tag != 'script':
|
||||
return
|
||||
if self._in_style and tag != 'style':
|
||||
return
|
||||
|
||||
# Parse style attribute if present
|
||||
style = {}
|
||||
if 'style' in attrs_dict:
|
||||
style = self._parse_style(attrs_dict['style'])
|
||||
|
||||
# Apply tag-specific styling based on the tag
|
||||
tag_style = self._get_tag_style(tag)
|
||||
for key, value in tag_style.items():
|
||||
if key not in style:
|
||||
style[key] = value
|
||||
|
||||
# Push the current style and apply the new style
|
||||
self._push_style(style)
|
||||
|
||||
# Handle specific tags
|
||||
if tag == 'html':
|
||||
# Set document language if specified
|
||||
if 'lang' in attrs_dict:
|
||||
self.document.set_metadata(MetadataType.LANGUAGE, attrs_dict['lang'])
|
||||
|
||||
elif tag == 'head':
|
||||
self._in_head = True
|
||||
|
||||
elif tag == 'title' and self._in_head:
|
||||
self._in_title = True
|
||||
self._title_buffer = ""
|
||||
|
||||
elif tag == 'meta' and self._in_head:
|
||||
self._handle_meta_tag(attrs_dict)
|
||||
|
||||
elif tag == 'link' and self._in_head:
|
||||
self._handle_link_tag(attrs_dict)
|
||||
|
||||
elif tag == 'script':
|
||||
self._in_script = True
|
||||
self._script_buffer = ""
|
||||
|
||||
elif tag == 'style':
|
||||
self._in_style = True
|
||||
self._style_buffer = ""
|
||||
|
||||
elif tag == 'body':
|
||||
# Body attributes can contain style information
|
||||
pass
|
||||
|
||||
elif tag == 'p':
|
||||
self._flush_text() # Flush any pending text
|
||||
self._current_paragraph = Paragraph()
|
||||
|
||||
# Add the paragraph to the current block or document
|
||||
if self._current_block and hasattr(self._current_block, 'add_block'):
|
||||
self._current_block.add_block(self._current_paragraph)
|
||||
else:
|
||||
self.document.add_block(self._current_paragraph)
|
||||
|
||||
# Push to block stack
|
||||
self._block_stack.append(self._current_paragraph)
|
||||
self._current_block = self._current_paragraph
|
||||
|
||||
elif tag in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6'):
|
||||
self._flush_text() # Flush any pending text
|
||||
|
||||
# Determine heading level
|
||||
level_map = {
|
||||
'h1': HeadingLevel.H1,
|
||||
'h2': HeadingLevel.H2,
|
||||
'h3': HeadingLevel.H3,
|
||||
'h4': HeadingLevel.H4,
|
||||
'h5': HeadingLevel.H5,
|
||||
'h6': HeadingLevel.H6
|
||||
}
|
||||
|
||||
heading = Heading(level=level_map[tag])
|
||||
|
||||
# Add the heading to the current block or document
|
||||
if self._current_block and hasattr(self._current_block, 'add_block'):
|
||||
self._current_block.add_block(heading)
|
||||
else:
|
||||
self.document.add_block(heading)
|
||||
|
||||
# Push to block stack
|
||||
self._block_stack.append(heading)
|
||||
self._current_block = heading
|
||||
self._current_paragraph = heading # Heading inherits from Paragraph
|
||||
|
||||
elif tag == 'div':
|
||||
self._flush_text() # Flush any pending text
|
||||
|
||||
# For divs, we create a new paragraph as a container
|
||||
div_para = Paragraph()
|
||||
|
||||
# Add the div to the current block or document
|
||||
if self._current_block and hasattr(self._current_block, 'add_block'):
|
||||
self._current_block.add_block(div_para)
|
||||
else:
|
||||
self.document.add_block(div_para)
|
||||
|
||||
# Push to block stack
|
||||
self._block_stack.append(div_para)
|
||||
self._current_block = div_para
|
||||
self._current_paragraph = div_para
|
||||
|
||||
elif tag == 'blockquote':
|
||||
self._flush_text() # Flush any pending text
|
||||
|
||||
quote = Quote()
|
||||
|
||||
# Add the quote to the current block or document
|
||||
if self._current_block and hasattr(self._current_block, 'add_block'):
|
||||
self._current_block.add_block(quote)
|
||||
else:
|
||||
self.document.add_block(quote)
|
||||
|
||||
# Push to block stack
|
||||
self._block_stack.append(quote)
|
||||
self._current_block = quote
|
||||
|
||||
elif tag == 'pre':
|
||||
self._flush_text() # Flush any pending text
|
||||
|
||||
# Pre can optionally contain a code block
|
||||
# We'll create a paragraph for now, and if we find a code tag inside,
|
||||
# we'll replace it with a code block
|
||||
pre_para = Paragraph()
|
||||
|
||||
# Add the pre to the current block or document
|
||||
if self._current_block and hasattr(self._current_block, 'add_block'):
|
||||
self._current_block.add_block(pre_para)
|
||||
else:
|
||||
self.document.add_block(pre_para)
|
||||
|
||||
# Push to block stack
|
||||
self._block_stack.append(pre_para)
|
||||
self._current_block = pre_para
|
||||
self._current_paragraph = pre_para
|
||||
|
||||
elif tag == 'code':
|
||||
# If we're inside a pre, replace the paragraph with a code block
|
||||
if self._block_stack and isinstance(self._block_stack[-1], Paragraph):
|
||||
pre_para = self._block_stack.pop()
|
||||
|
||||
# Get the language from class if specified (e.g., class="language-python")
|
||||
language = ""
|
||||
if 'class' in attrs_dict:
|
||||
class_attr = attrs_dict['class']
|
||||
if class_attr.startswith('language-'):
|
||||
language = class_attr[9:]
|
||||
|
||||
code_block = CodeBlock(language=language)
|
||||
|
||||
# Replace the paragraph with the code block
|
||||
if pre_para.parent:
|
||||
parent = pre_para.parent
|
||||
if hasattr(parent, '_blocks'):
|
||||
# Find the paragraph in the parent's blocks and replace it
|
||||
for i, block in enumerate(parent._blocks):
|
||||
if block == pre_para:
|
||||
parent._blocks[i] = code_block
|
||||
break
|
||||
|
||||
# Push the code block to the stack
|
||||
self._block_stack.append(code_block)
|
||||
self._current_block = code_block
|
||||
self._current_paragraph = None
|
||||
else:
|
||||
# If not in a pre, just create a formatted span for code
|
||||
self._current_span = None # Force creation of a new span with code style
|
||||
|
||||
elif tag in ('ul', 'ol', 'dl'):
|
||||
self._flush_text() # Flush any pending text
|
||||
|
||||
# Determine list style
|
||||
style_map = {
|
||||
'ul': ListStyle.UNORDERED,
|
||||
'ol': ListStyle.ORDERED,
|
||||
'dl': ListStyle.DEFINITION
|
||||
}
|
||||
|
||||
list_block = HList(style=style_map[tag])
|
||||
|
||||
# Add the list to the current block or document
|
||||
if self._current_block and hasattr(self._current_block, 'add_block'):
|
||||
self._current_block.add_block(list_block)
|
||||
else:
|
||||
self.document.add_block(list_block)
|
||||
|
||||
# Push to block stack and list stack
|
||||
self._block_stack.append(list_block)
|
||||
self._list_stack.append(list_block)
|
||||
self._current_block = list_block
|
||||
self._current_paragraph = None
|
||||
|
||||
elif tag == 'li' and self._list_stack:
|
||||
self._flush_text() # Flush any pending text
|
||||
|
||||
list_item = ListItem()
|
||||
|
||||
# Add to the current list
|
||||
current_list = self._list_stack[-1]
|
||||
current_list.add_item(list_item)
|
||||
|
||||
# Push to block stack
|
||||
self._block_stack.append(list_item)
|
||||
self._current_block = list_item
|
||||
self._current_paragraph = None
|
||||
|
||||
elif tag == 'dt' and self._list_stack and self._list_stack[-1].style == ListStyle.DEFINITION:
|
||||
self._flush_text() # Flush any pending text
|
||||
|
||||
# For definition term, we create a list item with a term
|
||||
list_item = ListItem(term="") # Will be filled by content
|
||||
|
||||
# Add to the current list
|
||||
current_list = self._list_stack[-1]
|
||||
current_list.add_item(list_item)
|
||||
|
||||
# Push to block stack
|
||||
self._block_stack.append(list_item)
|
||||
self._current_block = list_item
|
||||
|
||||
# Create a paragraph for the term content
|
||||
term_para = Paragraph()
|
||||
list_item.add_block(term_para)
|
||||
self._current_paragraph = term_para
|
||||
|
||||
elif tag == 'dd' and self._list_stack and self._list_stack[-1].style == ListStyle.DEFINITION:
|
||||
self._flush_text() # Flush any pending text
|
||||
|
||||
# Find the last dt item
|
||||
current_list = self._list_stack[-1]
|
||||
if current_list._items:
|
||||
list_item = current_list._items[-1]
|
||||
|
||||
# Create a paragraph for the description content
|
||||
desc_para = Paragraph()
|
||||
list_item.add_block(desc_para)
|
||||
|
||||
# Update current state
|
||||
self._current_paragraph = desc_para
|
||||
else:
|
||||
# If no dt found, create a new list item
|
||||
list_item = ListItem()
|
||||
current_list.add_item(list_item)
|
||||
|
||||
# Push to block stack
|
||||
self._block_stack.append(list_item)
|
||||
self._current_block = list_item
|
||||
|
||||
# Create a paragraph for the description content
|
||||
desc_para = Paragraph()
|
||||
list_item.add_block(desc_para)
|
||||
self._current_paragraph = desc_para
|
||||
|
||||
elif tag == 'table':
|
||||
self._flush_text() # Flush any pending text
|
||||
|
||||
# Create a new table
|
||||
caption = None
|
||||
if 'summary' in attrs_dict:
|
||||
caption = attrs_dict['summary']
|
||||
|
||||
table = Table(caption=caption)
|
||||
|
||||
# Add the table to the current block or document
|
||||
if self._current_block and hasattr(self._current_block, 'add_block'):
|
||||
self._current_block.add_block(table)
|
||||
else:
|
||||
self.document.add_block(table)
|
||||
|
||||
# Push to block stack and table stack
|
||||
self._block_stack.append(table)
|
||||
self._table_stack.append(table)
|
||||
self._current_block = table
|
||||
self._current_paragraph = None
|
||||
|
||||
elif tag in ('thead', 'tbody', 'tfoot') and self._table_stack:
|
||||
# Just track the current section - no need to create new objects
|
||||
self._current_table_section = tag
|
||||
|
||||
elif tag == 'tr' and self._table_stack:
|
||||
self._flush_text() # Flush any pending text
|
||||
|
||||
# Create a new row
|
||||
row = TableRow()
|
||||
|
||||
# Add to the current table
|
||||
current_table = self._table_stack[-1]
|
||||
|
||||
# Determine the section based on context
|
||||
section = "body"
|
||||
if hasattr(self, '_current_table_section'):
|
||||
if self._current_table_section == 'thead':
|
||||
section = "header"
|
||||
elif self._current_table_section == 'tfoot':
|
||||
section = "footer"
|
||||
|
||||
current_table.add_row(row, section=section)
|
||||
|
||||
# Update state
|
||||
self._current_table_row = row
|
||||
self._current_paragraph = None
|
||||
|
||||
elif tag in ('td', 'th') and self._current_table_row:
|
||||
self._flush_text() # Flush any pending text
|
||||
|
||||
# Parse attributes
|
||||
colspan = 1
|
||||
rowspan = 1
|
||||
|
||||
if 'colspan' in attrs_dict:
|
||||
try:
|
||||
colspan = int(attrs_dict['colspan'])
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if 'rowspan' in attrs_dict:
|
||||
try:
|
||||
rowspan = int(attrs_dict['rowspan'])
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Create a new cell
|
||||
is_header = (tag == 'th')
|
||||
cell = TableCell(is_header=is_header, colspan=colspan, rowspan=rowspan)
|
||||
|
||||
# Add to the current row
|
||||
self._current_table_row.add_cell(cell)
|
||||
|
||||
# Push to block stack
|
||||
self._block_stack.append(cell)
|
||||
self._current_block = cell
|
||||
|
||||
# Create a paragraph for the cell content
|
||||
cell_para = Paragraph()
|
||||
cell.add_block(cell_para)
|
||||
self._current_paragraph = cell_para
|
||||
|
||||
elif tag == 'a':
|
||||
self._flush_text() # Flush any pending text
|
||||
|
||||
# Parse attributes
|
||||
href = attrs_dict.get('href', '')
|
||||
title = attrs_dict.get('title', '')
|
||||
|
||||
# Determine link type
|
||||
link_type = LinkType.INTERNAL
|
||||
if href.startswith('http://') or href.startswith('https://'):
|
||||
link_type = LinkType.EXTERNAL
|
||||
elif href.startswith('javascript:'):
|
||||
link_type = LinkType.FUNCTION
|
||||
elif href.startswith('api:'):
|
||||
link_type = LinkType.API
|
||||
href = href[4:] # Remove api: prefix
|
||||
|
||||
# If we have a base URL and the href is relative, resolve it
|
||||
if self._base_url and not href.startswith(('http://', 'https://', 'javascript:', 'api:', '#')):
|
||||
href = urllib.parse.urljoin(self._base_url, href)
|
||||
|
||||
# Create a Link object
|
||||
self._current_link = Link(
|
||||
location=href,
|
||||
link_type=link_type,
|
||||
title=title if title else None
|
||||
)
|
||||
|
||||
# Set the flag to indicate we're inside a link
|
||||
self._in_link = True
|
||||
|
||||
# Force creation of a new span with link style
|
||||
self._current_span = None
|
||||
|
||||
elif tag == 'img':
|
||||
# Handle image
|
||||
src = attrs_dict.get('src', '')
|
||||
alt = attrs_dict.get('alt', '')
|
||||
|
||||
# Parse width and height if provided
|
||||
width = None
|
||||
height = None
|
||||
if 'width' in attrs_dict:
|
||||
try:
|
||||
width = int(attrs_dict['width'])
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if 'height' in attrs_dict:
|
||||
try:
|
||||
height = int(attrs_dict['height'])
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# If we have a base URL and the src is relative, resolve it
|
||||
if self._base_url and not src.startswith(('http://', 'https://')):
|
||||
src = urllib.parse.urljoin(self._base_url, src)
|
||||
|
||||
# Create an Image block
|
||||
from .abstract.block import Image
|
||||
image = Image(source=src, alt_text=alt, width=width, height=height)
|
||||
|
||||
# Add the image to the current block or document
|
||||
if self._current_block and hasattr(self._current_block, 'add_block'):
|
||||
self._current_block.add_block(image)
|
||||
else:
|
||||
self.document.add_block(image)
|
||||
|
||||
# Also add as a resource for backwards compatibility
|
||||
resource_name = f"img_{len(self.document._resources) + 1}"
|
||||
self.document.add_resource(resource_name, {
|
||||
'type': 'image',
|
||||
'src': src,
|
||||
'alt': alt,
|
||||
'width': width,
|
||||
'height': height,
|
||||
'image_object': image
|
||||
})
|
||||
|
||||
elif tag == 'br':
|
||||
|
||||
# Add a line break
|
||||
if self._current_paragraph:
|
||||
line_break = LineBreak()
|
||||
if hasattr(self._current_paragraph, 'add_block'):
|
||||
self._current_paragraph.add_block(line_break)
|
||||
|
||||
# Flush any text before the break
|
||||
self._flush_text()
|
||||
|
||||
elif tag == 'hr':
|
||||
self._flush_text() # Flush any pending text
|
||||
|
||||
# Create a horizontal rule
|
||||
hr = HorizontalRule()
|
||||
|
||||
# Add to the current block or document
|
||||
if self._current_block and hasattr(self._current_block, 'add_block'):
|
||||
self._current_block.add_block(hr)
|
||||
else:
|
||||
self.document.add_block(hr)
|
||||
|
||||
elif tag in ('b', 'strong'):
|
||||
# Bold text
|
||||
self._current_style['font_weight'] = FontWeight.BOLD
|
||||
self._current_span = None # Force creation of a new span
|
||||
|
||||
elif tag in ('i', 'em'):
|
||||
# Italic text
|
||||
self._current_style['font_style'] = FontStyle.ITALIC
|
||||
self._current_span = None # Force creation of a new span
|
||||
|
||||
elif tag == 'u':
|
||||
# Underlined text
|
||||
self._current_style['decoration'] = TextDecoration.UNDERLINE
|
||||
self._current_span = None # Force creation of a new span
|
||||
|
||||
elif tag == 'span':
|
||||
# Span can have style attributes
|
||||
self._current_span = None # Force creation of a new span
|
||||
|
||||
elif tag == 'form':
|
||||
self._flush_text() # Flush any pending text
|
||||
|
||||
# Parse attributes
|
||||
form_id = attrs_dict.get('id', f"form_{len(self.document._resources) + 1}")
|
||||
action = attrs_dict.get('action', '')
|
||||
|
||||
# Create a Form object
|
||||
form = Form(form_id=form_id, action=action)
|
||||
|
||||
# Add as a resource
|
||||
self.document.add_resource(form_id, form)
|
||||
|
||||
# TODO: Create a proper Form block class and add it to the document
|
||||
|
||||
elif tag == 'input':
|
||||
# Parse attributes
|
||||
input_type = attrs_dict.get('type', 'text')
|
||||
input_name = attrs_dict.get('name', '')
|
||||
input_value = attrs_dict.get('value', '')
|
||||
input_required = 'required' in attrs_dict
|
||||
|
||||
# Map HTML input types to FormFieldType
|
||||
type_map = {
|
||||
'text': FormFieldType.TEXT,
|
||||
'password': FormFieldType.PASSWORD,
|
||||
'checkbox': FormFieldType.CHECKBOX,
|
||||
'radio': FormFieldType.RADIO,
|
||||
'number': FormFieldType.NUMBER,
|
||||
'date': FormFieldType.DATE,
|
||||
'time': FormFieldType.TIME,
|
||||
'email': FormFieldType.EMAIL,
|
||||
'url': FormFieldType.URL,
|
||||
'color': FormFieldType.COLOR,
|
||||
'range': FormFieldType.RANGE,
|
||||
'hidden': FormFieldType.HIDDEN
|
||||
}
|
||||
|
||||
field_type = type_map.get(input_type, FormFieldType.TEXT)
|
||||
|
||||
# Create a FormField object
|
||||
field = FormField(
|
||||
name=input_name,
|
||||
field_type=field_type,
|
||||
label=attrs_dict.get('placeholder', input_name),
|
||||
value=input_value,
|
||||
required=input_required
|
||||
)
|
||||
|
||||
# TODO: Add the field to a form if inside a form
|
||||
|
||||
elif tag == 'textarea':
|
||||
# Similar to input but with multiline content
|
||||
# We'll handle the content in handle_data
|
||||
pass
|
||||
|
||||
elif tag == 'select':
|
||||
# Similar to input but with options
|
||||
# We'll handle the options in handle_data
|
||||
pass
|
||||
|
||||
elif tag == 'button':
|
||||
# Parse attributes
|
||||
button_type = attrs_dict.get('type', 'button')
|
||||
button_name = attrs_dict.get('name', '')
|
||||
|
||||
# TODO: Create a Button object and add it to the document
|
||||
|
||||
def handle_endtag(self, tag: str):
|
||||
"""
|
||||
Handle the end of an HTML tag.
|
||||
|
||||
Args:
|
||||
tag: The tag name
|
||||
"""
|
||||
tag = tag.lower()
|
||||
|
||||
# Special handling for elements where we collect content
|
||||
if tag == 'script' and self._in_script:
|
||||
self._in_script = False
|
||||
self.document.add_script(self._script_buffer)
|
||||
self._script_buffer = ""
|
||||
self._pop_style()
|
||||
return
|
||||
|
||||
if tag == 'style' and self._in_style:
|
||||
self._in_style = False
|
||||
# Parse the style and add to document
|
||||
stylesheet = self._parse_css(self._style_buffer)
|
||||
if stylesheet:
|
||||
self.document.add_stylesheet(stylesheet)
|
||||
self._style_buffer = ""
|
||||
self._pop_style()
|
||||
return
|
||||
|
||||
if tag == 'title' and self._in_title:
|
||||
self._in_title = False
|
||||
self.document.set_title(self._title_buffer.strip())
|
||||
self._title_buffer = ""
|
||||
self._pop_style()
|
||||
return
|
||||
|
||||
if self._in_script and tag != 'script':
|
||||
return
|
||||
if self._in_style and tag != 'style':
|
||||
return
|
||||
|
||||
# Flush any accumulated text
|
||||
self._flush_text()
|
||||
|
||||
# Handle specific end tags
|
||||
if tag == 'head':
|
||||
self._in_head = False
|
||||
|
||||
elif tag == 'body':
|
||||
pass # Nothing special to do
|
||||
|
||||
elif tag in ('p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre'):
|
||||
# Pop from block stack
|
||||
if self._block_stack:
|
||||
self._block_stack.pop()
|
||||
|
||||
# Update current block
|
||||
if self._block_stack:
|
||||
self._current_block = self._block_stack[-1]
|
||||
else:
|
||||
self._current_block = None
|
||||
|
||||
# Reset current paragraph
|
||||
self._current_paragraph = None
|
||||
self._current_span = None
|
||||
|
||||
elif tag == 'code':
|
||||
# If we're inside a code block, no need to do anything special
|
||||
pass
|
||||
|
||||
elif tag in ('ul', 'ol', 'dl'):
|
||||
# Pop from block stack and list stack
|
||||
if self._block_stack:
|
||||
self._block_stack.pop()
|
||||
|
||||
if self._list_stack:
|
||||
self._list_stack.pop()
|
||||
|
||||
# Update current block
|
||||
if self._block_stack:
|
||||
self._current_block = self._block_stack[-1]
|
||||
else:
|
||||
self._current_block = None
|
||||
|
||||
# Reset current paragraph
|
||||
self._current_paragraph = None
|
||||
self._current_span = None
|
||||
|
||||
elif tag in ('li', 'dt', 'dd'):
|
||||
# Pop from block stack
|
||||
if self._block_stack:
|
||||
self._block_stack.pop()
|
||||
|
||||
# Update current block
|
||||
if self._block_stack:
|
||||
self._current_block = self._block_stack[-1]
|
||||
else:
|
||||
self._current_block = None
|
||||
|
||||
# Reset current paragraph
|
||||
self._current_paragraph = None
|
||||
self._current_span = None
|
||||
|
||||
elif tag == 'table':
|
||||
# Pop from block stack and table stack
|
||||
if self._block_stack:
|
||||
self._block_stack.pop()
|
||||
|
||||
if self._table_stack:
|
||||
self._table_stack.pop()
|
||||
|
||||
# Update current block
|
||||
if self._block_stack:
|
||||
self._current_block = self._block_stack[-1]
|
||||
else:
|
||||
self._current_block = None
|
||||
|
||||
# Reset current paragraph and table state
|
||||
self._current_paragraph = None
|
||||
self._current_span = None
|
||||
self._current_table_row = None
|
||||
if hasattr(self, '_current_table_section'):
|
||||
delattr(self, '_current_table_section')
|
||||
|
||||
elif tag in ('thead', 'tbody', 'tfoot'):
|
||||
# Clear current section
|
||||
if hasattr(self, '_current_table_section'):
|
||||
delattr(self, '_current_table_section')
|
||||
|
||||
elif tag == 'tr':
|
||||
# Reset current row
|
||||
self._current_table_row = None
|
||||
|
||||
elif tag in ('td', 'th'):
|
||||
# Pop from block stack
|
||||
if self._block_stack:
|
||||
self._block_stack.pop()
|
||||
|
||||
# Update current block
|
||||
if self._block_stack:
|
||||
self._current_block = self._block_stack[-1]
|
||||
else:
|
||||
self._current_block = None
|
||||
|
||||
# Reset current paragraph
|
||||
self._current_paragraph = None
|
||||
self._current_span = None
|
||||
|
||||
elif tag == 'a':
|
||||
# End of link
|
||||
self._in_link = False
|
||||
self._current_link = None
|
||||
|
||||
elif tag in ('b', 'strong', 'i', 'em', 'u', 'span'):
|
||||
# End of styled text
|
||||
self._current_span = None
|
||||
|
||||
# Pop style regardless of tag
|
||||
self._pop_style()
|
||||
|
||||
def handle_data(self, data: str):
|
||||
"""
|
||||
Handle text data.
|
||||
|
||||
Args:
|
||||
data: The text data
|
||||
"""
|
||||
if self._in_script:
|
||||
self._script_buffer += data
|
||||
return
|
||||
|
||||
if self._in_style:
|
||||
self._style_buffer += data
|
||||
return
|
||||
|
||||
if self._in_title:
|
||||
self._title_buffer += data
|
||||
return
|
||||
|
||||
# Add to text buffer
|
||||
self._text_buffer += data
|
||||
|
||||
def handle_entityref(self, name: str):
|
||||
"""
|
||||
Handle an HTML entity reference.
|
||||
|
||||
Args:
|
||||
name: The entity name
|
||||
"""
|
||||
# Map common entity references to characters
|
||||
entities = {
|
||||
'lt': '<',
|
||||
'gt': '>',
|
||||
'amp': '&',
|
||||
'quot': '"',
|
||||
'apos': "'",
|
||||
'nbsp': ' ',
|
||||
'copy': '©',
|
||||
'reg': '®',
|
||||
'trade': '™',
|
||||
}
|
||||
|
||||
if name in entities:
|
||||
char = entities[name]
|
||||
else:
|
||||
try:
|
||||
import html.entities
|
||||
char = chr(html.entities.name2codepoint[name])
|
||||
except (KeyError, ImportError):
|
||||
char = f'&{name};'
|
||||
|
||||
# Handle based on context
|
||||
if self._in_script:
|
||||
self._script_buffer += char
|
||||
elif self._in_style:
|
||||
self._style_buffer += char
|
||||
elif self._in_title:
|
||||
self._title_buffer += char
|
||||
else:
|
||||
self._text_buffer += char
|
||||
|
||||
def handle_charref(self, name: str):
|
||||
"""
|
||||
Handle a character reference.
|
||||
|
||||
Args:
|
||||
name: The character reference (decimal or hex)
|
||||
"""
|
||||
# Convert character reference to character
|
||||
if name.startswith('x'):
|
||||
# Hexadecimal reference
|
||||
char = chr(int(name[1:], 16))
|
||||
else:
|
||||
# Decimal reference
|
||||
char = chr(int(name))
|
||||
|
||||
# Handle based on context
|
||||
if self._in_script:
|
||||
self._script_buffer += char
|
||||
elif self._in_style:
|
||||
self._style_buffer += char
|
||||
elif self._in_title:
|
||||
self._title_buffer += char
|
||||
else:
|
||||
self._text_buffer += char
|
||||
|
||||
def _push_style(self, style: Dict[str, Any]):
|
||||
"""
|
||||
Push a new style onto the style stack.
|
||||
|
||||
Args:
|
||||
style: The style to push
|
||||
"""
|
||||
# Save the current style
|
||||
self._style_stack.append(self._current_style.copy())
|
||||
|
||||
# Apply the new style
|
||||
for key, value in style.items():
|
||||
self._current_style[key] = value
|
||||
|
||||
def _pop_style(self):
|
||||
"""Pop a style from the style stack."""
|
||||
if self._style_stack:
|
||||
self._current_style = self._style_stack.pop()
|
||||
|
||||
def _get_tag_style(self, tag: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Get the default style for a tag.
|
||||
|
||||
Args:
|
||||
tag: The tag name
|
||||
|
||||
Returns:
|
||||
A dictionary of style properties
|
||||
"""
|
||||
# Default styles for common tags
|
||||
tag_styles = {
|
||||
'h1': {'font_size': 24, 'font_weight': FontWeight.BOLD},
|
||||
'h2': {'font_size': 20, 'font_weight': FontWeight.BOLD},
|
||||
'h3': {'font_size': 18, 'font_weight': FontWeight.BOLD},
|
||||
'h4': {'font_size': 16, 'font_weight': FontWeight.BOLD},
|
||||
'h5': {'font_size': 14, 'font_weight': FontWeight.BOLD},
|
||||
'h6': {'font_size': 12, 'font_weight': FontWeight.BOLD},
|
||||
'b': {'font_weight': FontWeight.BOLD},
|
||||
'strong': {'font_weight': FontWeight.BOLD},
|
||||
'i': {'font_style': FontStyle.ITALIC},
|
||||
'em': {'font_style': FontStyle.ITALIC},
|
||||
'u': {'decoration': TextDecoration.UNDERLINE},
|
||||
'a': {'decoration': TextDecoration.UNDERLINE, 'color': (0, 0, 255)},
|
||||
'code': {'font_family': 'monospace', 'background': (240, 240, 240, 255)},
|
||||
'pre': {'font_family': 'monospace'},
|
||||
}
|
||||
|
||||
return tag_styles.get(tag, {})
|
||||
|
||||
def _create_font(self) -> Font:
|
||||
"""
|
||||
Create a Font object from the current style.
|
||||
|
||||
Returns:
|
||||
Font: A font object with the current style settings
|
||||
"""
|
||||
@ -1 +0,0 @@
|
||||
## list langauges
|
||||
@ -1,176 +0,0 @@
|
||||
# this should contain classes for how different object can be rendered, e.g. bold, italic, regular
|
||||
from PIL import ImageFont
|
||||
from enum import Enum
|
||||
from typing import Tuple, Union, Optional
|
||||
|
||||
|
||||
class FontWeight(Enum):
|
||||
NORMAL = "normal"
|
||||
BOLD = "bold"
|
||||
|
||||
|
||||
class FontStyle(Enum):
|
||||
NORMAL = "normal"
|
||||
ITALIC = "italic"
|
||||
|
||||
|
||||
class TextDecoration(Enum):
|
||||
NONE = "none"
|
||||
UNDERLINE = "underline"
|
||||
STRIKETHROUGH = "strikethrough"
|
||||
|
||||
|
||||
class Font:
|
||||
"""
|
||||
Font class to manage text rendering properties including font face, size, color, and styling.
|
||||
This class is used by the text renderer to determine how to render text.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
font_path: Optional[str] = None,
|
||||
font_size: int = 12,
|
||||
colour: Tuple[int, int, int] = (0, 0, 0),
|
||||
weight: FontWeight = FontWeight.NORMAL,
|
||||
style: FontStyle = FontStyle.NORMAL,
|
||||
decoration: TextDecoration = TextDecoration.NONE,
|
||||
background: Optional[Tuple[int, int, int, int]] = None,
|
||||
language = "en_EN"):
|
||||
"""
|
||||
Initialize a Font object with the specified properties.
|
||||
|
||||
Args:
|
||||
font_path: Path to the font file (.ttf, .otf). If None, uses default font.
|
||||
font_size: Size of the font in points.
|
||||
colour: RGB color tuple for the text.
|
||||
weight: Font weight (normal or bold).
|
||||
style: Font style (normal or italic).
|
||||
decoration: Text decoration (none, underline, or strikethrough).
|
||||
background: RGBA background color for the text. If None, transparent background.
|
||||
"""
|
||||
self._font_path = font_path
|
||||
self._font_size = font_size
|
||||
self._colour = colour
|
||||
self._weight = weight
|
||||
self._style = style
|
||||
self._decoration = decoration
|
||||
self._background = background if background else (255, 255, 255, 0)
|
||||
self.language = language
|
||||
# Load the font file or use default
|
||||
self._load_font()
|
||||
|
||||
def _load_font(self):
|
||||
"""Load the font using PIL's ImageFont"""
|
||||
try:
|
||||
if self._font_path:
|
||||
self._font = ImageFont.truetype(
|
||||
self._font_path,
|
||||
self._font_size
|
||||
)
|
||||
else:
|
||||
# Use default font
|
||||
self._font = ImageFont.load_default()
|
||||
if self._font_size != 12: # Default size might not be 12
|
||||
self._font = ImageFont.truetype(self._font.path, self._font_size)
|
||||
except Exception as e:
|
||||
print(f"Error loading font: {e}")
|
||||
self._font = ImageFont.load_default()
|
||||
|
||||
@property
|
||||
def font(self):
|
||||
"""Get the PIL ImageFont object"""
|
||||
return self._font
|
||||
|
||||
@property
|
||||
def font_size(self):
|
||||
"""Get the font size"""
|
||||
return self._font_size
|
||||
|
||||
@property
|
||||
def colour(self):
|
||||
"""Get the text color"""
|
||||
return self._colour
|
||||
|
||||
@property
|
||||
def color(self):
|
||||
"""Alias for colour (American spelling)"""
|
||||
return self._colour
|
||||
|
||||
@property
|
||||
def background(self):
|
||||
"""Get the background color"""
|
||||
return self._background
|
||||
|
||||
@property
|
||||
def weight(self):
|
||||
"""Get the font weight"""
|
||||
return self._weight
|
||||
|
||||
@property
|
||||
def style(self):
|
||||
"""Get the font style"""
|
||||
return self._style
|
||||
|
||||
@property
|
||||
def decoration(self):
|
||||
"""Get the text decoration"""
|
||||
return self._decoration
|
||||
|
||||
def with_size(self, size: int):
|
||||
"""Create a new Font object with modified size"""
|
||||
return Font(
|
||||
self._font_path,
|
||||
size,
|
||||
self._colour,
|
||||
self._weight,
|
||||
self._style,
|
||||
self._decoration,
|
||||
self._background
|
||||
)
|
||||
|
||||
def with_colour(self, colour: Tuple[int, int, int]):
|
||||
"""Create a new Font object with modified colour"""
|
||||
return Font(
|
||||
self._font_path,
|
||||
self._font_size,
|
||||
colour,
|
||||
self._weight,
|
||||
self._style,
|
||||
self._decoration,
|
||||
self._background
|
||||
)
|
||||
|
||||
def with_weight(self, weight: FontWeight):
|
||||
"""Create a new Font object with modified weight"""
|
||||
return Font(
|
||||
self._font_path,
|
||||
self._font_size,
|
||||
self._colour,
|
||||
weight,
|
||||
self._style,
|
||||
self._decoration,
|
||||
self._background
|
||||
)
|
||||
|
||||
def with_style(self, style: FontStyle):
|
||||
"""Create a new Font object with modified style"""
|
||||
return Font(
|
||||
self._font_path,
|
||||
self._font_size,
|
||||
self._colour,
|
||||
self._weight,
|
||||
style,
|
||||
self._decoration,
|
||||
self._background
|
||||
)
|
||||
|
||||
def with_decoration(self, decoration: TextDecoration):
|
||||
"""Create a new Font object with modified decoration"""
|
||||
return Font(
|
||||
self._font_path,
|
||||
self._font_size,
|
||||
self._colour,
|
||||
self._weight,
|
||||
self._style,
|
||||
decoration,
|
||||
self._background
|
||||
)
|
||||
@ -1,137 +0,0 @@
|
||||
from pyWebLayout.base import Renderable
|
||||
from .concrete.box import Box
|
||||
from pyWebLayout.layout import Alignment
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
|
||||
class TableCell(Box):
|
||||
def __init__(self, origin, size, content: Optional[Renderable] = None,
|
||||
callback=None, sheet=None, mode=None,
|
||||
halign=Alignment.CENTER, valign=Alignment.CENTER,
|
||||
padding: Tuple[int, int, int, int] = (5, 5, 5, 5)):
|
||||
"""
|
||||
Initialize a table cell.
|
||||
|
||||
Args:
|
||||
origin: Top-left corner coordinates
|
||||
size: Width and height of the cell
|
||||
content: Optional renderable content to place in the cell
|
||||
callback: Optional callback function
|
||||
sheet: Optional image sheet
|
||||
mode: Optional image mode
|
||||
halign: Horizontal alignment
|
||||
valign: Vertical alignment
|
||||
padding: Padding as (top, right, bottom, left)
|
||||
"""
|
||||
super().__init__(origin, size, callback, sheet, mode, halign, valign)
|
||||
self._content = content
|
||||
self._padding = padding # (top, right, bottom, left)
|
||||
|
||||
def set_content(self, content: Renderable):
|
||||
"""Set the content of this cell"""
|
||||
self._content = content
|
||||
|
||||
def render(self) -> Image:
|
||||
"""Render the cell with its content and border"""
|
||||
# Create the base canvas
|
||||
canvas = super().render()
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
|
||||
# Draw border (optional - can be customized)
|
||||
draw.rectangle([(0, 0), tuple(self._size - np.array([1, 1]))],
|
||||
outline=(0, 0, 0), width=1)
|
||||
|
||||
return canvas
|
||||
|
||||
|
||||
class Table(Box):
|
||||
def __init__(self, rows: int, columns: int, origin, size,
|
||||
cell_padding: Tuple[int, int, int, int] = (5, 5, 5, 5),
|
||||
callback=None, sheet=None, mode=None,
|
||||
halign=Alignment.CENTER, valign=Alignment.CENTER):
|
||||
"""
|
||||
Initialize a table with specified number of rows and columns.
|
||||
|
||||
Args:
|
||||
rows: Number of rows in the table
|
||||
columns: Number of columns in the table
|
||||
origin: Top-left corner coordinates
|
||||
size: Width and height of the table
|
||||
cell_padding: Padding for each cell as (top, right, bottom, left)
|
||||
callback: Optional callback function
|
||||
sheet: Optional image sheet
|
||||
mode: Optional image mode
|
||||
halign: Horizontal alignment
|
||||
valign: Vertical alignment
|
||||
"""
|
||||
super().__init__(origin, size, callback, sheet, mode, halign, valign)
|
||||
|
||||
self._rows = rows
|
||||
self._columns = columns
|
||||
self._cell_padding = cell_padding
|
||||
|
||||
# Calculate cell dimensions
|
||||
cell_width = size[0] // columns
|
||||
cell_height = size[1] // rows
|
||||
|
||||
# Create a 2D array of cells
|
||||
self._cells: List[List[TableCell]] = []
|
||||
|
||||
for row in range(rows):
|
||||
cell_row = []
|
||||
for col in range(columns):
|
||||
# Calculate cell position
|
||||
cell_origin = np.array([col * cell_width, row * cell_height])
|
||||
cell_size = np.array([cell_width, cell_height])
|
||||
|
||||
# Create the cell
|
||||
cell = TableCell(
|
||||
origin=cell_origin,
|
||||
size=cell_size,
|
||||
sheet=sheet,
|
||||
mode=mode,
|
||||
halign=halign,
|
||||
valign=valign,
|
||||
padding=cell_padding
|
||||
)
|
||||
|
||||
cell_row.append(cell)
|
||||
|
||||
self._cells.append(cell_row)
|
||||
|
||||
def add_to_cell(self, x: int, y: int, content: Renderable):
|
||||
"""
|
||||
Add content to a specific cell in the table.
|
||||
|
||||
Args:
|
||||
x: Column index (0-based)
|
||||
y: Row index (0-based)
|
||||
content: Renderable content to add to the cell
|
||||
"""
|
||||
if 0 <= y < self._rows and 0 <= x < self._columns:
|
||||
self._cells[y][x].set_content(content)
|
||||
else:
|
||||
raise IndexError(f"Cell indices ({x}, {y}) out of range. Table is {self._columns}x{self._rows}")
|
||||
|
||||
def render(self) -> Image:
|
||||
"""Render the complete table with all cells"""
|
||||
# Create base canvas
|
||||
canvas = super().render()
|
||||
|
||||
# Render each cell and paste it onto the canvas
|
||||
for row in range(self._rows):
|
||||
for col in range(self._columns):
|
||||
cell = self._cells[row][col]
|
||||
cell_img = cell.render()
|
||||
|
||||
# Get the position for this cell
|
||||
cell_pos = (col * (self._size[0] // self._columns),
|
||||
row * (self._size[1] // self._rows))
|
||||
|
||||
# Paste the cell onto the canvas
|
||||
canvas.paste(cell_img, cell_pos, cell_img)
|
||||
|
||||
return canvas
|
||||
@ -17,6 +17,7 @@ dependencies = [
|
||||
"numpy",
|
||||
"pyphen",
|
||||
"beautifulsoup4",
|
||||
"flask"
|
||||
]
|
||||
|
||||
[tool.coverage.run]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user