Skip to content

Commit db7da9a

Browse files
committed
improve attribute formatting with better argument parsing
1 parent 88859c8 commit db7da9a

6 files changed

Lines changed: 351 additions & 36 deletions

File tree

phpdotnet/phd/Package/Generic/XHTML.php

Lines changed: 136 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,23 +1493,150 @@ public function format_modifier_text($value, $tag) {
14931493
}
14941494

14951495
private function format_attribute_modifier_text(string $value): string {
1496-
// Anything that is not a leading "#[\Attribute(" / "#[\Attribute]" chunk
1497-
// e.g. "|" separator between arguments passes through.
1498-
if (!preg_match('/^(#\[)(.+?)([](])$/', $value, $match)) {
1499-
if (trim($value) === '|') {
1500-
return ' | ';
1496+
$trimmed = trim($value);
1497+
$namePattern = '\\\\?[A-Za-z_][A-Za-z0-9_\\\\]*';
1498+
1499+
// Full attribute with literal arguments: #[\Name(args)]
1500+
if (preg_match('/^(#\[)(' . $namePattern . ')\((.+)\)]$/s', $trimmed, $match)) {
1501+
[, $prefix, $name, $args] = $match;
1502+
return $prefix . $this->link_attribute_name($name) . '(' . $this->render_attribute_args($args) . ')]';
1503+
}
1504+
1505+
// Simple attribute: #[\Name]
1506+
if (preg_match('/^#\[(' . $namePattern . ')]$/', $trimmed, $match)) {
1507+
$name = $match[1];
1508+
$attribute = strtolower(ltrim($name, "\\"));
1509+
$href = $this->getFilename('class.' . $attribute);
1510+
$token = '#[' . $name . ']';
1511+
if (!$href) {
1512+
return $token;
15011513
}
1502-
return $value;
1514+
1515+
return '<a href="' . $href . $this->getExt() . '">' . $token . '</a> ';
15031516
}
15041517

1505-
[, $prefix, $name, $suffix] = $match;
1518+
// Opening of an attribute followed by child elements: #[\Name(
1519+
if (preg_match('/^(#\[)(' . $namePattern . ')\($/', $trimmed, $match)) {
1520+
[, $prefix, $name] = $match;
1521+
return $prefix . $this->link_attribute_name($name) . '(';
1522+
}
1523+
1524+
// Separator between attribute arguments
1525+
if ($trimmed === '|') {
1526+
return ' | ';
1527+
}
1528+
1529+
return $trimmed === '' ? '' : $value;
1530+
}
1531+
1532+
private function link_attribute_name(string $name): string {
15061533
$attribute = strtolower(ltrim($name, "\\"));
15071534
$href = $this->getFilename('class.' . $attribute);
15081535
if (!$href) {
1509-
return $value;
1536+
return $name;
1537+
}
1538+
1539+
return '<a href="' . $href . $this->getExt() . '">' . $name . '</a>';
1540+
}
1541+
1542+
private function render_attribute_args(string $args): string {
1543+
$args = trim(preg_replace('/\s+/', ' ', $args));
1544+
$parts = $this->split_attribute_args($args);
1545+
1546+
if (count($parts) <= 1) {
1547+
return $this->link_constants_in_text($parts[0]['value'] ?? '');
1548+
}
1549+
1550+
$lines = [];
1551+
$prefix = '';
1552+
foreach ($parts as $part) {
1553+
$rendered = $this->link_constants_in_text($part['value']);
1554+
$line = '&nbsp;&nbsp;&nbsp;&nbsp;' . $prefix . $rendered;
1555+
if ($part['separator'] === ',') {
1556+
$line .= ',';
1557+
$prefix = '';
1558+
} elseif ($part['separator'] === '|') {
1559+
$prefix = '| ';
1560+
} else {
1561+
$prefix = '';
1562+
}
1563+
$lines[] = $line;
1564+
}
1565+
1566+
return '<br>' . implode('<br>', $lines) . '<br>';
1567+
}
1568+
1569+
/**
1570+
* @return list<array{value: string, separator: string|null}>
1571+
*/
1572+
private function split_attribute_args(string $args): array {
1573+
$parts = [];
1574+
$current = '';
1575+
$depth = 0;
1576+
$inString = false;
1577+
$stringChar = '';
1578+
$length = strlen($args);
1579+
1580+
for ($i = 0; $i < $length; $i++) {
1581+
$ch = $args[$i];
1582+
1583+
if ($inString) {
1584+
$current .= $ch;
1585+
if ($ch === '\\' && $i + 1 < $length) {
1586+
$current .= $args[++$i];
1587+
continue;
1588+
}
1589+
if ($ch === $stringChar) {
1590+
$inString = false;
1591+
}
1592+
continue;
1593+
}
1594+
1595+
if ($ch === '"' || $ch === "'") {
1596+
$inString = true;
1597+
$stringChar = $ch;
1598+
$current .= $ch;
1599+
continue;
1600+
}
1601+
1602+
if ($ch === '(' || $ch === '[') {
1603+
$depth++;
1604+
} elseif ($ch === ')' || $ch === ']') {
1605+
$depth--;
1606+
}
1607+
1608+
if ($depth === 0 && ($ch === '|' || $ch === ',')) {
1609+
$parts[] = ['value' => trim($current), 'separator' => $ch];
1610+
$current = '';
1611+
continue;
1612+
}
1613+
1614+
$current .= $ch;
1615+
}
1616+
1617+
if (trim($current) !== '') {
1618+
$parts[] = ['value' => trim($current), 'separator' => null];
15101619
}
15111620

1512-
return $prefix . '<a href="' . $href . $this->getExt() . '">' . $name . '</a>' . $suffix;
1621+
return $parts;
1622+
}
1623+
1624+
private function link_constants_in_text(string $text): string {
1625+
$escaped = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
1626+
1627+
// Link Class::CONST references that appear as literal text
1628+
return preg_replace_callback(
1629+
'/\\\\?[A-Za-z_][\w\\\\]*::[A-Za-z_][\w]*/',
1630+
function (array $match): string {
1631+
$constant = $match[0];
1632+
$link = $this->createLink($this->convertConstantNameToId($constant));
1633+
if ($link === null) {
1634+
return $constant;
1635+
}
1636+
return '<a href="' . $link . '">' . $constant . '</a>';
1637+
},
1638+
$escaped,
1639+
);
15131640
}
15141641

15151642
public function format_methodsynopsis($open, $name, $attrs, $props) {

tests/package/generic/attribute_formatting_001.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Content:
4242

4343
<div class="section">
4444
<p class="para">2. Class methodparameter with known attribute</p>
45-
<div class="constructorsynopsis dc-description"><span class="modifier">public</span> <span class="methodname">mysqli::__construct</span>(<span class="methodparam"><span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><span class="type"><span class="type">string</span><span class="type">null</span></span> <code class="parameter">$password</code><span class="initializer"> = <span class="type">null</span></span></span>)</div>
45+
<div class="constructorsynopsis dc-description"><span class="modifier">public</span> <span class="methodname">mysqli::__construct</span>(<span class="methodparam"><span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><span class="type"><span class="type">string</span><span class="type">null</span></span> <code class="parameter">$password</code><span class="initializer"> = <span class="type">null</span></span></span>)</div>
4646

4747
</div>
4848

@@ -54,7 +54,7 @@ Content:
5454

5555
<div class="section">
5656
<p class="para">4. Function parameter with known attribute</p>
57-
<div class="methodsynopsis dc-description"><span class="type">bool</span> <span class="methodname">password_verify</span>(<span class="methodparam"><span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><span class="type">string</span> <code class="parameter">$password</code></span>, <span class="methodparam"><span class="type">string</span> <code class="parameter">$hash</code></span>)</div>
57+
<div class="methodsynopsis dc-description"><span class="type">bool</span> <span class="methodname">password_verify</span>(<span class="methodparam"><span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><span class="type">string</span> <code class="parameter">$password</code></span>, <span class="methodparam"><span class="type">string</span> <code class="parameter">$hash</code></span>)</div>
5858

5959
</div>
6060
</div>

tests/package/generic/attribute_formatting_002.phpt

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Content:
5050
<div class="section">
5151
<p class="para">1. Class with unknown attributes</p>
5252
<div class="classsynopsis"><div class="classsynopsisinfo">
53-
53+
5454
<span class="attribute">#[\UnknownAttribute]</span><br>
5555
<span class="attribute">#[\AnotherUnknownAttribute]</span><br>
5656
<span class="modifier">class</span> <strong class="classname">DateTime</strong>
@@ -61,9 +61,9 @@ Content:
6161
<div class="section">
6262
<p class="para">2. Class with known attributes</p>
6363
<div class="classsynopsis"><div class="classsynopsisinfo">
64-
65-
<span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
66-
<span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
64+
65+
<span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
66+
<span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
6767
<span class="modifier">class</span> <strong class="classname">DateTime</strong>
6868
{</div>
6969
}</div>
@@ -80,8 +80,8 @@ Content:
8080

8181
<div class="section">
8282
<p class="para">4. Method with known attributes</p>
83-
<div class="methodsynopsis dc-description"><span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
84-
<span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
83+
<div class="methodsynopsis dc-description"><span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
84+
<span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
8585
<span class="modifier">public</span> <span class="methodname">ClassName::methodName</span>()</div>
8686

8787
</div>
@@ -96,16 +96,16 @@ Content:
9696

9797
<div class="section">
9898
<p class="para">6. Constructor with known attributes</p>
99-
<div class="constructorsynopsis dc-description"><span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
100-
<span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
99+
<div class="constructorsynopsis dc-description"><span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
100+
<span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
101101
<span class="modifier">public</span> <span class="methodname">ClassName::__construct</span>()</div>
102102

103103
</div>
104104

105105
<div class="section">
106106
<p class="para">7. Class, constructor and methods with unknown attributes</p>
107107
<div class="classsynopsis"><div class="classsynopsisinfo">
108-
108+
109109
<span class="attribute">#[\UnknownAttribute]</span><br>
110110
<span class="attribute">#[\AnotherUnknownAttribute]</span><br>
111111
<span class="modifier">class</span> <strong class="classname">DateTime</strong>
@@ -129,22 +129,22 @@ Content:
129129
<div class="section">
130130
<p class="para">8. Class, constructor and methods with known attributes</p>
131131
<div class="classsynopsis"><div class="classsynopsisinfo">
132-
133-
<span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
134-
<span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
132+
133+
<span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
134+
<span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
135135
<span class="modifier">class</span> <strong class="classname">DateTime</strong>
136136
{</div>
137137
<div class="constructorsynopsis dc-description">
138-
<span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
139-
<span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
138+
<span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
139+
<span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
140140
<span class="modifier">public</span> <span class="methodname">ClassName::__construct</span>()</div>
141141

142-
<div class="methodsynopsis dc-description"><span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
143-
<span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
142+
<div class="methodsynopsis dc-description"><span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
143+
<span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
144144
<span class="modifier">public</span> <span class="methodname">ClassName::methodName1</span>()</div>
145145

146-
<div class="methodsynopsis dc-description"><span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
147-
<span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
146+
<div class="methodsynopsis dc-description"><span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
147+
<span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
148148
<span class="modifier">public</span> <span class="methodname">ClassName::methodName2</span>()</div>
149149

150150
}</div>
@@ -161,8 +161,8 @@ Content:
161161

162162
<div class="section">
163163
<p class="para">10. Function with known attributes</p>
164-
<div class="methodsynopsis dc-description"><span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
165-
<span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
164+
<div class="methodsynopsis dc-description"><span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
165+
<span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
166166
<span class="type">void</span> <span class="methodname">function_name</span>)</div>
167167

168168
</div>

tests/package/generic/attribute_formatting_003.phpt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Content:
5252
<div class="section">
5353
<p class="para">1. Property/Constant with unknown attributes</p>
5454
<div class="classsynopsis"><div class="classsynopsisinfo">
55-
55+
5656
<span class="modifier">class</span> <strong class="classname">ClassName</strong>
5757
{</div>
5858
<div class="classsynopsisinfo classsynopsisinfo_comment">/* Properties/Constants */</div>
@@ -70,13 +70,13 @@ Content:
7070
<div class="section">
7171
<p class="para">2. Property/Constant with known attributes</p>
7272
<div class="classsynopsis"><div class="classsynopsisinfo">
73-
73+
7474
<span class="modifier">class</span> <strong class="classname">ClassName</strong>
7575
{</div>
7676
<div class="classsynopsisinfo classsynopsisinfo_comment">/* Properties/Constants */</div>
7777
<div class="fieldsynopsis">
78-
<span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
79-
<span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
78+
<span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
79+
<span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
8080
<span class="modifier">public</span>
8181
<span class="modifier">readonly</span>
8282
<span class="type">string</span>
@@ -88,7 +88,7 @@ Content:
8888
<div class="section">
8989
<p class="para">3. Constant of a class with attribute</p>
9090
<div class="classsynopsis"><div class="classsynopsisinfo">
91-
91+
9292
<span class="attribute">#[\UnknownAttribute]</span><br>
9393
<span class="modifier">class</span> <strong class="classname">ClassName</strong>
9494
{</div>

0 commit comments

Comments
 (0)