diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache
new file mode 100644
index 0000000..521368b
--- /dev/null
+++ b/.php-cs-fixer.cache
@@ -0,0 +1 @@
+{"php":"8.4.18","version":"3.95.3:v3.95.3#3d681493acc0e93283481b1c63c263737df78687","indent":" ","lineEnding":"\n","rules":{"align_multiline_comment":{"comment_type":"phpdocs_only"},"array_indentation":true,"array_syntax":{"syntax":"short"},"binary_operator_spaces":{"default":"single_space"},"blank_line_before_statement":{"statements":["if","return","while","for","foreach","do"]},"blank_line_after_opening_tag":true,"blank_line_after_namespace":true,"full_opening_tag":true,"fully_qualified_strict_types":true,"line_ending":true,"linebreak_after_opening_tag":true,"lowercase_cast":true,"lowercase_keywords":true,"lowercase_static_reference":true,"no_alternative_syntax":true,"no_blank_lines_after_class_opening":true,"no_blank_lines_after_phpdoc":true,"no_blank_lines_before_namespace":true,"no_closing_tag":true,"no_empty_comment":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_multiline_whitespace_around_double_arrow":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_unused_imports":true,"single_import_per_statement":true,"single_blank_line_at_eof":true,"no_whitespace_in_blank_line":true,"ordered_imports":{"sort_algorithm":"alpha"},"ordered_class_elements":{"sort_algorithm":"alpha","order":["constant_public","constant_protected","constant_private","property_public","property_protected","property_private","construct","method_public","method_private"]},"no_mixed_echo_print":{"use":"echo"},"constant_case":{"case":"lower"},"increment_style":{"style":"post"},"concat_space":{"spacing":"none"},"braces":{"allow_single_line_closure":false,"position_after_functions_and_oop_constructs":"same","position_after_anonymous_constructs":"next","position_after_control_structures":"same"},"class_definition":{"single_line":true}},"ruleCustomisationPolicyVersion":"null-policy","hashes":{"WebFiori\/Ui\/ListItem.php":"ea7d937d7f0f87b387231e19f408bac4","WebFiori\/Ui\/Input.php":"a32571b47302e88abe1a1b593f05c580","WebFiori\/Ui\/Exceptions\/TemplateNotFoundException.php":"0525fa91afe1452e9e2cb3817a5d20d0","WebFiori\/Ui\/Exceptions\/InvalidNodeNameException.php":"34dcd7ac55b28f1038b910cf4b1d6294","WebFiori\/Ui\/TemplateCompiler.php":"a7729de8052c9b71d8d6eda1da60f18d","WebFiori\/Ui\/TableRow.php":"c6dfd353a53a6b1ea0fa373cabf6ac99","WebFiori\/Ui\/Br.php":"874d09a7145347cbdbac3f893272bc4e","WebFiori\/Ui\/JsCode.php":"a70c5cabef6bd72ee89a55b10f88fead","WebFiori\/Ui\/Label.php":"408765b9d4a663229900d37e95c4e11d","WebFiori\/Ui\/HTMLList.php":"bcdc411a01b7ebc745e46a36b6871162","WebFiori\/Ui\/TableCell.php":"c622079f8a69e271fbcbcdb2b8e5fbf4","WebFiori\/Ui\/HTMLTable.php":"2d3cf67e93ab3e465cc5c57efe107cce","WebFiori\/Ui\/HeadNode.php":"d6ca40f301ba32df712304ff3d43d294","WebFiori\/Ui\/RadioGroup.php":"eaa8804e05590ad82ca3d7ffd3ec48c6","WebFiori\/Ui\/HTMLNode.php":"3fbee82bf49c0982ec60c94737c8dab7","WebFiori\/Ui\/Anchor.php":"d037b740c3115c48c44d92a296c50f6b","WebFiori\/Ui\/UnorderedList.php":"7f4c6f8b415392a764b1ab116c89ad90","WebFiori\/Ui\/OrderedList.php":"ed8d4fd70121b5d6411acf0a42604c91","WebFiori\/Ui\/HTMLDoc.php":"680405abb4862b29f467211bcc83afec","WebFiori\/Ui\/CodeSnippet.php":"c4cd75c3cf5d61d065160401d8198c3b","WebFiori\/Ui\/Paragraph.php":"779c96103576311cec2b08e20c229c1a"}}
\ No newline at end of file
diff --git a/WebFiori/Ui/Anchor.php b/WebFiori/Ui/Anchor.php
index 4920d0d..06a7b4c 100644
--- a/WebFiori/Ui/Anchor.php
+++ b/WebFiori/Ui/Anchor.php
@@ -1,4 +1,5 @@
parseStylePair($current, $retVal);
+ $current = '';
+ } else {
+ $current .= $ch;
+ }
+ }
+
+ if (trim($current) !== '') {
+ $this->parseStylePair($current, $retVal);
}
return $retVal;
@@ -1651,7 +1670,7 @@ public function open() : string {
$valType = gettype($val);
$quoted = $this->isQuotedAttribute();
- if (!$quoted && $valType == "integer" || $valType == 'double') {
+ if (!$quoted && ($valType == "integer" || $valType == 'double')) {
$retVal .= ' '.$attr.'='.$val;
} else {
if ($val != '' && !$quoted && strpos($val, '?') === false
@@ -1661,7 +1680,7 @@ public function open() : string {
&& strpos($val, '-') === false) {
$retVal .= ' '.$attr.'='.$val;
} else {
- $retVal .= ' '.$attr.'="'.str_replace('"', '\"', $val).'"';
+ $retVal .= ' '.$attr.'="'.str_replace(['&', '"'], ['&', '"'], $val).'"';
}
}
}
@@ -1910,15 +1929,18 @@ public function setAttribute(string $name, mixed $val = null) : HTMLNode {
} else if ($val === null) {
$this->attributes[$trimmedName] = null;
} else if ($attrValType == 'string') {
- $this->attributes[$trimmedName] = $trimmedVal;
+ $this->attributes[$trimmedName] = $trimmedVal;
} else if (in_array($attrValType, ['double', 'integer'])) {
$this->attributes[$trimmedName] = $val;
} else if ($attrValType == 'boolean') {
$this->attributes[$trimmedName] = $val === true ? 'true' : 'false';
}
+ } else {
+ throw new \InvalidArgumentException("Invalid attribute name: '$trimmedName'.");
}
}
+
return $this;
}
/**
@@ -2494,7 +2516,6 @@ private function closeAsCode(array $FO) : string {
} else {
return '</'.$this->getNodeName().'>';
}
-
}
/**
*
@@ -2640,6 +2661,32 @@ private function openAsCode(array $FO) : string {
return $retVal;
}
+ /**
+ * Validates the name of the node.
+ *
+ * @param string $name The name of the node in lower case.
+ *
+ * @return bool If the name is valid, the method will return true. If
+ * it is not valid, it will return false. Valid values must follow the
+ * following rules:
+ *
+ *
Must not be an empty string.
+ *
Must not start with a number.
+ *
Must not start with '-'.
+ *
Can only have the following characters in its name: [A-Z], [a-z],
+ * [0-9], ':', '@' and '-'.
+ *
+ *
+ */
+ private function parseStylePair(string $pair, array &$result): void {
+ $colonPos = strpos($pair, ':');
+
+ if ($colonPos !== false) {
+ $key = trim(substr($pair, 0, $colonPos));
+ $val = trim(substr($pair, $colonPos + 1));
+ $result[$key] = $val;
+ }
+ }
private function popNode() {
$node = $this->nodesStack->pop();
@@ -2800,23 +2847,6 @@ private function removeChHelper($node) {
private function setParentHelper(?HTMLNode $node) {
$this->parentNode = $node;
}
- /**
- * Validates the name of the node.
- *
- * @param string $name The name of the node in lower case.
- *
- * @return bool If the name is valid, the method will return true. If
- * it is not valid, it will return false. Valid values must follow the
- * following rules:
- *
- *
Must not be an empty string.
- *
Must not start with a number.
- *
Must not start with '-'.
- *
Can only have the following characters in its name: [A-Z], [a-z],
- * [0-9], ':', '@' and '-'.
- *
- *
- */
private function validateAttrNameHelper(string $name) : bool {
$nameLen = strlen($name);
@@ -2882,6 +2912,7 @@ private function validateFormattingOptions(array $FO): array {
} else {
$FO['tab-spaces'] = self::DEFAULT_CODE_FORMAT['tab-spaces'];
}
+
//initial tab validation
if (gettype($FO['initial-tab']) == 'integer' && $FO['initial-tab'] < 0) {
$FO['initial-tab'] = 0;
diff --git a/WebFiori/Ui/HTMLTable.php b/WebFiori/Ui/HTMLTable.php
index 8c151ba..96339c2 100644
--- a/WebFiori/Ui/HTMLTable.php
+++ b/WebFiori/Ui/HTMLTable.php
@@ -1,4 +1,5 @@
'collapse'
]);
- for ($x = 0 ; $x < $this->rows() ; $x++) {
+ for ($x = 0 ; $x < $this->rows ; $x++) {
$row = new TableRow();
- for ($y = 0 ; $y < $this->cols() ; $y++) {
+ for ($y = 0 ; $y < $this->cols ; $y++) {
$row->addCell('');
}
$this->addRow($row);
@@ -108,7 +109,9 @@ public function addRow(TableRow|array $arrOrRowObj) {
* @return int Number of columns in the table.
*/
public function cols() : int {
- return $this->cols;
+ $firstRow = $this->getChild(0);
+
+ return $firstRow !== null ? $firstRow->childrenCount() : 0;
}
/**
* Returns a table cell given its indices.
@@ -204,11 +207,11 @@ public function removeCol(int $colIndex) : array {
public function removeRow(int $rowIndex) {
if ($this->rows() > 1) {
$row = $this->removeChild($rowIndex);
-
+
if ($row !== null) {
$this->rows--;
}
-
+
return $row;
}
}
@@ -218,7 +221,7 @@ public function removeRow(int $rowIndex) {
* @return int Number of rows in the table.
*/
public function rows() : int {
- return $this->rows;
+ return $this->childrenCount();
}
/**
* Sets the attributes of cells in one specific column.
diff --git a/WebFiori/Ui/HeadNode.php b/WebFiori/Ui/HeadNode.php
index 9eaa41d..6f70172 100644
--- a/WebFiori/Ui/HeadNode.php
+++ b/WebFiori/Ui/HeadNode.php
@@ -1,4 +1,5 @@
getMeta($trimmedName, true);
-
- if ($meta !== null && $override === true) {
- $meta->setAttribute('content', $content);
-
- return $this;
- } else if ($meta === null) {
- $meta = new HTMLNode('meta');
- $meta->setAttribute('http-equiv', $trimmedName);
- $meta->setAttribute('content', $content);
- $this->insertMetaInCorrectOrder($meta);
- }
- }
-
- return $this;
- }
- private function insertMetaInCorrectOrder(HTMLNode $newMeta) {
- $insertPosition = -1;
-
- for ($x = 0 ; $x < $this->childrenCount() ; $x++) {
- $chNode = $this->getChild($x);
-
- if ($chNode->getNodeName() == 'meta') {
- $insertPosition = $x;
- }
- }
-
- if ($insertPosition != -1) {
- $this->insert($newMeta, $insertPosition + 1);
- } else {
- $this->addChild($newMeta);
- }
- }
/**
* Adds new meta tag.
*
@@ -525,6 +489,26 @@ public function addMeta(string $name, string $content, bool $override = false) :
return $this;
}
+ public function addMetaHttpEquiv(string $name, string $content, bool $override = false) : HeadNode {
+ $trimmedName = trim(strtolower($name));
+
+ if (strlen($trimmedName) != 0) {
+ $meta = $this->getMeta($trimmedName, true);
+
+ if ($meta !== null && $override === true) {
+ $meta->setAttribute('content', $content);
+
+ return $this;
+ } else if ($meta === null) {
+ $meta = new HTMLNode('meta');
+ $meta->setAttribute('http-equiv', $trimmedName);
+ $meta->setAttribute('content', $content);
+ $this->insertMetaInCorrectOrder($meta);
+ }
+ }
+
+ return $this;
+ }
/**
* Adds a set of meta tags.
*
@@ -708,7 +692,7 @@ public function getLinkNodes() : LinkedList {
public function getMeta(string $name, bool $httpEquvi = false) {
$lName = strtolower(trim($name));
$attribute = $httpEquvi ? 'http-equiv' : 'name';
-
+
if ($lName == 'charset') {
return $this->getCharsetNode();
} else {
@@ -1115,4 +1099,21 @@ private function cssJsInsertHelper(HTMLNode $node, $otherAttrs) {
$this->addChild($node);
}
}
+ private function insertMetaInCorrectOrder(HTMLNode $newMeta) {
+ $insertPosition = -1;
+
+ for ($x = 0 ; $x < $this->childrenCount() ; $x++) {
+ $chNode = $this->getChild($x);
+
+ if ($chNode->getNodeName() == 'meta') {
+ $insertPosition = $x;
+ }
+ }
+
+ if ($insertPosition != -1) {
+ $this->insert($newMeta, $insertPosition + 1);
+ } else {
+ $this->addChild($newMeta);
+ }
+ }
}
diff --git a/WebFiori/Ui/Input.php b/WebFiori/Ui/Input.php
index d177e68..9a276a4 100644
--- a/WebFiori/Ui/Input.php
+++ b/WebFiori/Ui/Input.php
@@ -1,4 +1,5 @@
getType() == 'php') {
- ob_start();
- extract($varsToPass, EXTR_SKIP);
- require $this->getPath();
- $this->rawOutput = ob_get_clean();
+ $this->rawOutput = (static function(string $__path, array $__vars)
+ {
+ extract($__vars, EXTR_SKIP);
+ ob_start();
+ try {
+ require $__path;
+
+ return ob_get_clean();
+ } catch (\Throwable $e) {
+ ob_end_clean();
+ throw $e;
+ }
+ })($this->getPath(), $varsToPass);
} else {
$this->rawOutput = file_get_contents($this->getPath());
}
@@ -136,11 +146,11 @@ public function compile(array $varsToPass = []) {
*/
public static function fromHTMLText(string $text, bool $asHTMLDocObj = true) {
$nodesArr = self::htmlAsArray($text);
-
+
if (count($nodesArr) >= 1) {
$TN = 'tag-name';
$retVal = [];
-
+
if ($asHTMLDocObj && ($nodesArr[0][$TN] == 'html' || $nodesArr[0][$TN] == '!DOCTYPE')) {
$retVal = self::parseHTMLDoc($nodesArr);
} else {
@@ -152,45 +162,6 @@ public static function fromHTMLText(string $text, bool $asHTMLDocObj = true) {
return null;
}
- private static function parseHTMLNode($nodesArr) {
- if (count($nodesArr) != 1) {
- $retVal = [];
- foreach ($nodesArr as $node) {
- $asHtmlNode = self::fromHTMLTextHelper00($node);
- $retVal[] = $asHtmlNode;
- }
- return $retVal;
- } else {
- return self::fromHTMLTextHelper00($nodesArr[0]);
- }
- }
- private static function parseHTMLDoc($children) : HTMLDoc {
- $retVal = new HTMLDoc();
- $retVal->getHeadNode()->removeAllChildNodes();
- $retVal->getBody()->removeAttributes();
- $TN = 'tag-name';
-
- for ($x = 0 ; $x < count($children) ; $x++) {
- if ($children[$x][$TN] == 'html') {
- $htmlNode = self::fromHTMLTextHelper00($children[$x]);
-
- for ($y = 0 ; $y < $htmlNode->childrenCount() ; $y++) {
- $child = $htmlNode->children()->get($y);
-
- if ($child->getNodeName() == 'head') {
- $retVal->setHeadNode($child);
- } else if ($child->getNodeName() == 'body') {
- for ($z = 0 ; $z < $child->childrenCount() ; $z++) {
- $node = $child->children()->get($z);
- $retVal->addChild($node);
- }
-
- }
- }
- }
- }
- return $retVal;
- }
/**
* Returns an array that contains directories names of the calling files.
*
@@ -317,7 +288,7 @@ public function getType() : string {
*/
public static function htmlAsArray(string $text) : array {
$cleanedHtmlArr = self::replaceAttrsValues($text);
- $trimmed = str_replace(' $val) {
- $htmlNode->getTitleNode()->setAttribute($attr, $val);
+ self::safeSetAttribute($htmlNode->getTitleNode(), $attr, $val);
}
} else if ($chNode[$TN] == 'base') {
$isBaseSet = false;
@@ -533,7 +504,7 @@ private static function fromHTMLTextHelper00(array $nodeArr) {
if ($isBaseSet) {
foreach ($chNode['attributes'] as $attr => $val) {
- $htmlNode->getBaseNode()->setAttribute($attr, $val);
+ self::safeSetAttribute($htmlNode->getBaseNode(), $attr, $val);
}
}
} else if ($chNode[$TN] == 'link') {
@@ -541,14 +512,14 @@ private static function fromHTMLTextHelper00(array $nodeArr) {
$tmpNode = new HTMLNode('link');
foreach ($chNode['attributes'] as $attr => $val) {
- $tmpNode->setAttribute($attr, $val);
+ self::safeSetAttribute($tmpNode, $attr, $val);
$lower = strtolower($val);
if ($attr == 'rel' && $lower == 'canonical') {
$isCanonical = true;
- $tmpNode->setAttribute($attr, $lower);
+ self::safeSetAttribute($tmpNode, $attr, $lower);
} else if ($attr == 'rel' && $lower == 'stylesheet') {
- $tmpNode->setAttribute($attr, $lower);
+ self::safeSetAttribute($tmpNode, $attr, $lower);
}
}
@@ -557,7 +528,7 @@ private static function fromHTMLTextHelper00(array $nodeArr) {
if ($isCanonicalSet) {
foreach ($tmpNode->getAttributes() as $attr => $val) {
- $htmlNode->getCanonicalNode()->setAttribute($attr, $val);
+ self::safeSetAttribute($htmlNode->getCanonicalNode(), $attr, $val);
}
}
} else {
@@ -567,11 +538,11 @@ private static function fromHTMLTextHelper00(array $nodeArr) {
$tmpNode = self::fromHTMLTextHelper00($chNode);
foreach ($tmpNode->getAttributes() as $attr => $val) {
- $tmpNode->setAttribute($attr, $val);
+ self::safeSetAttribute($tmpNode, $attr, $val);
$lower = strtolower($val);
if ($attr == 'type' && $lower == 'text/javascript') {
- $tmpNode->setAttribute($attr, $lower);
+ self::safeSetAttribute($tmpNode, $attr, $lower);
}
}
$htmlNode->addChild($tmpNode);
@@ -594,7 +565,7 @@ private static function fromHTMLTextHelper00(array $nodeArr) {
if (isset($nodeArr['attributes'])) {
foreach ($nodeArr['attributes'] as $key => $value) {
- $htmlNode->setAttribute($key, $value);
+ self::safeSetAttribute($htmlNode, $key, $value);
}
}
@@ -751,6 +722,56 @@ private static function parseAttributesHelper(Queue $queue, bool $isEqualFound,
$queue->enqueue($val);
$val = '';
}
+ private static function parseHTMLDoc($children) : HTMLDoc {
+ $retVal = new HTMLDoc();
+ $retVal->getHeadNode()->removeAllChildNodes();
+ $retVal->getBody()->removeAttributes();
+ $TN = 'tag-name';
+
+ for ($x = 0 ; $x < count($children) ; $x++) {
+ if ($children[$x][$TN] == 'html') {
+ $htmlNode = self::fromHTMLTextHelper00($children[$x]);
+
+
+ foreach ($htmlNode->getAttributes() as $attr => $val) {
+ self::safeSetAttribute($retVal->getDocumentRoot(), $attr, $val);
+ }
+
+ for ($y = 0 ; $y < $htmlNode->childrenCount() ; $y++) {
+ $child = $htmlNode->children()->get($y);
+
+ if ($child->getNodeName() == 'head') {
+ $retVal->setHeadNode($child);
+ } else if ($child->getNodeName() == 'body') {
+ for ($z = 0 ; $z < $child->childrenCount() ; $z++) {
+ $node = $child->children()->get($z);
+ $retVal->addChild($node);
+ }
+
+ foreach ($child->getAttributes() as $attr => $val) {
+ self::safeSetAttribute($retVal->getBody(), $attr, $val);
+ }
+ }
+ }
+ }
+ }
+
+ return $retVal;
+ }
+ private static function parseHTMLNode($nodesArr) {
+ if (count($nodesArr) != 1) {
+ $retVal = [];
+
+ foreach ($nodesArr as $node) {
+ $asHtmlNode = self::fromHTMLTextHelper00($node);
+ $retVal[] = $asHtmlNode;
+ }
+
+ return $retVal;
+ } else {
+ return self::fromHTMLTextHelper00($nodesArr[0]);
+ }
+ }
/**
* Replace all attributes values in HTML string with a hash.
*
@@ -769,12 +790,12 @@ private static function parseAttributesHelper(Queue $queue, bool $isEqualFound,
*/
private static function replaceAttrsValues(string $htmlStr) : array {
$scripts = [];
- preg_match_all("/(?<=';
+ $result = TemplateCompiler::fromHTMLText($html);
+ $this->assertEquals($html, $result->toHTML());
+ }
+ /**
+ * @test
+ * Issue #56: Body and HTML attributes lost when parsing full documents
+ */
+ public function testDocumentAttributesPreserved() {
+ $html = 'Test