Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 205 additions & 9 deletions phpdotnet/phd/Package/Generic/XHTML.php
Original file line number Diff line number Diff line change
Expand Up @@ -1493,23 +1493,219 @@ public function format_modifier_text($value, $tag) {
}

private function format_attribute_modifier_text(string $value): string {
// Anything that is not a leading "#[\Attribute(" / "#[\Attribute]" chunk
// e.g. "|" separator between arguments passes through.
if (!preg_match('/^(#\[)(.+?)([](])$/', $value, $match)) {
if (trim($value) === '|') {
return ' | ';
$trimmed = trim($value);
$namePattern = '\\\\?[A-Za-z_][A-Za-z0-9_\\\\]*';

// Full attribute with literal arguments: #[\Name(args)]
if (preg_match('/^(#\[)(' . $namePattern . ')\((.+)\)]$/s', $trimmed, $match)) {
[, $prefix, $name, $args] = $match;
return $prefix . $this->link_attribute_name($name) . '(' . $this->render_attribute_args($args) . ')]';
}

// Simple attribute: #[\Name]
if (preg_match('/^#\[(' . $namePattern . ')]$/', $trimmed, $match)) {
$name = $match[1];
$attribute = strtolower(ltrim($name, "\\"));
$href = $this->getFilename('class.' . $attribute);
$token = '#[' . $name . ']';
if (!$href) {
return $token;
}
return $value;

return '<a href="' . $href . $this->getExt() . '">' . $token . '</a> ';
}

// Opening of an attribute followed by child elements: #[\Name(
if (preg_match('/^(#\[)(' . $namePattern . ')\($/', $trimmed, $match)) {
[, $prefix, $name] = $match;
return $prefix . $this->link_attribute_name($name) . '(';
}

// Separator between attribute arguments
if ($trimmed === '|') {
return ' | ';
}

[, $prefix, $name, $suffix] = $match;
return $trimmed === '' ? '' : $value;
}

private function link_attribute_name(string $name): string {
$attribute = strtolower(ltrim($name, "\\"));
$href = $this->getFilename('class.' . $attribute);
if (!$href) {
return $value;
return $name;
}

return '<a href="' . $href . $this->getExt() . '">' . $name . '</a>';
}

private function render_attribute_args(string $args): string {
$args = trim(preg_replace('/\s+/', ' ', $args));
$parts = $this->split_attribute_args($args);

if (count($parts) <= 1) {
return $this->render_attribute_arg_value($parts[0]['value'] ?? '');
}

$lines = [];
$prefix = '';
foreach ($parts as $part) {
$rendered = $this->render_attribute_arg_value($part['value']);
$line = '&nbsp;&nbsp;&nbsp;&nbsp;' . $prefix . $rendered;
if ($part['separator'] === ',') {
$line .= ',';
$prefix = '';
} elseif ($part['separator'] === '|') {
$prefix = '| ';
} else {
$prefix = '';
}
$lines[] = $line;
}

return '<br>' . implode('<br>', $lines) . '<br>';
}

/**
* @return list<array{value: string, separator: string|null}>
*/
private function split_attribute_args(string $args): array {
$parts = [];
$current = '';
$depth = 0;
$inString = false;
$stringChar = '';
$length = strlen($args);

for ($i = 0; $i < $length; $i++) {
$ch = $args[$i];

if ($inString) {
$current .= $ch;
if ($ch === '\\' && $i + 1 < $length) {
$current .= $args[++$i];
continue;
}
if ($ch === $stringChar) {
$inString = false;
}
continue;
}

if ($ch === '"' || $ch === "'") {
$inString = true;
$stringChar = $ch;
$current .= $ch;
continue;
}

if ($ch === '(' || $ch === '[') {
$depth++;
} elseif ($ch === ')' || $ch === ']') {
$depth--;
}

if ($depth === 0 && ($ch === '|' || $ch === ',')) {
$parts[] = ['value' => trim($current), 'separator' => $ch];
$current = '';
continue;
}

$current .= $ch;
}

return $prefix . '<a href="' . $href . $this->getExt() . '">' . $name . '</a>' . $suffix;
if (trim($current) !== '') {
$parts[] = ['value' => trim($current), 'separator' => null];
}

return $parts;
}

private function render_attribute_arg_value(string $value): string {
$prefix = '';
if (preg_match('/^([A-Za-z_][A-Za-z0-9_]*)\s*:(?!:)\s*/', $value, $m)) {
$prefix = '<code class="parameter">' . $m[1] . '</code>: ';
$value = substr($value, strlen($m[0]));
}

$out = '';
$buffer = '';
$length = strlen($value);
for ($i = 0; $i < $length; $i++) {
$ch = $value[$i];
if ($ch !== '"' && $ch !== "'") {
$buffer .= $ch;
continue;
}

if ($buffer !== '') {
$out .= $this->format_attribute_non_string_segment($buffer);
$buffer = '';
}

$stringChar = $ch;
$literal = $ch;
$i++;
for (; $i < $length; $i++) {
$sc = $value[$i];
$literal .= $sc;
if ($sc === '\\' && $i + 1 < $length) {
$literal .= $value[++$i];
continue;
}
if ($sc === $stringChar) {
break;
}
}
$out .= '<span class="type string">' . htmlspecialchars($literal, ENT_NOQUOTES, 'UTF-8') . '</span>';
}
if ($buffer !== '') {
$out .= $this->format_attribute_non_string_segment($buffer);
}

return $prefix . $out;
}

private function format_attribute_non_string_segment(string $segment): string {
$linked = $this->link_constants_in_text($segment);

// Split out <a>...</a> regions so we don't touch their contents.
$parts = preg_split('/(<a [^>]*>[^<]*<\/a>)/', $linked, -1, PREG_SPLIT_DELIM_CAPTURE);
foreach ($parts as $i => $part) {
if ($i % 2 === 1) {
continue;
}
$part = preg_replace_callback(
'/\b(true|false|null)\b/i',
fn(array $m) => '<span class="type ' . strtolower($m[1]) . '">' . $m[1] . '</span>',
$part,
);
$part = preg_replace_callback(
'/(?<![\w.])-?\d+(\.\d+)?(?![\w.])/',
fn(array $m) => '<span class="type ' . (isset($m[1]) && $m[1] !== '' ? 'float' : 'int') . '">' . $m[0] . '</span>',
$part,
);
$parts[$i] = $part;
}
return implode('', $parts);
}

private function link_constants_in_text(string $text): string {
$escaped = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');

// Link Class::CONST references that appear as literal text
return preg_replace_callback(
'/\\\\?[A-Za-z_][\w\\\\]*::[A-Za-z_][\w]*/',
function (array $match): string {
$constant = $match[0];
$link = $this->createLink($this->convertConstantNameToId($constant));
if ($link === null) {
return $constant;
}
return '<a href="' . $link . '">' . $constant . '</a>';
},
$escaped,
);
}

public function format_methodsynopsis($open, $name, $attrs, $props) {
Expand Down
4 changes: 2 additions & 2 deletions tests/package/generic/attribute_formatting_001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Content:

<div class="section">
<p class="para">2. Class methodparameter with known attribute</p>
<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>
<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>

</div>

Expand All @@ -54,7 +54,7 @@ Content:

<div class="section">
<p class="para">4. Function parameter with known attribute</p>
<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>
<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>

</div>
</div>
34 changes: 17 additions & 17 deletions tests/package/generic/attribute_formatting_002.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ Content:
<p class="para">2. Class with known attributes</p>
<div class="classsynopsis"><div class="classsynopsisinfo">

<span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
<span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
<span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
<span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
<span class="modifier">class</span> <strong class="classname">DateTime</strong>
{</div>
}</div>
Expand All @@ -80,8 +80,8 @@ Content:

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

</div>
Expand All @@ -96,8 +96,8 @@ Content:

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

</div>
Expand Down Expand Up @@ -130,21 +130,21 @@ Content:
<p class="para">8. Class, constructor and methods with known attributes</p>
<div class="classsynopsis"><div class="classsynopsisinfo">

<span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
<span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
<span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
<span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
<span class="modifier">class</span> <strong class="classname">DateTime</strong>
{</div>
<div class="constructorsynopsis dc-description">
<span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
<span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
<span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
<span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
<span class="modifier">public</span> <span class="methodname">ClassName::__construct</span>()</div>

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

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

}</div>
Expand All @@ -161,10 +161,10 @@ Content:

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

</div>

</div>
</div>
6 changes: 3 additions & 3 deletions tests/package/generic/attribute_formatting_003.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ Content:
{</div>
<div class="classsynopsisinfo classsynopsisinfo_comment">/* Properties/Constants */</div>
<div class="fieldsynopsis">
<span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
<span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
<span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
<span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
<span class="modifier">public</span>
<span class="modifier">readonly</span>
<span class="type">string</span>
Expand All @@ -102,4 +102,4 @@ Content:
}</div>
</div>

</div>
</div>
Loading