$this->createOpenTag();
-
- $this->makeTag();
-
- $this->getNextChar();
+ $this
+ ->makeTag()
+ ->getNextChar();
return self::INITIAL_STATE;
-
} elseif (self::isSpacerChar($this->char)) {
//
tagId[0] == '?')
&& ($this->tagId != '?xml');
-
$doctypeTag = (strtoupper($this->tagId) == '!DOCTYPE');
if ($externalTag) {
@@ -577,19 +650,20 @@ private function startTagState()
);
} elseif ($doctypeTag) {
$this->setupTag(SgmlIgnoredTag::create());
- } else
+ } else {
$this->createOpenTag();
+ }
- if ($externalTag)
+ if ($externalTag) {
return self::EXTERNAL_TAG_STATE;
- elseif ($doctypeTag)
+ } elseif ($doctypeTag) {
return self::DOCTYPE_TAG_STATE;
- else {
- // don't eating spacer for external and doctype tags
- $this->getNextChar();
-
- return self::INSIDE_TAG_STATE;
}
+
+ // don't eating spacer for external and doctype tags
+ $this->getNextChar();
+
+ return self::INSIDE_TAG_STATE;
} else {
$char = $this->char;
@@ -599,10 +673,9 @@ private function startTagState()
//
$this->createOpenTag()->setEmpty(true);
-
- $this->makeTag();
-
- $this->getNextChar();
+ $this
+ ->makeTag()
+ ->getNextChar();
return self::INITIAL_STATE;
}
@@ -614,77 +687,70 @@ private function startTagState()
// ... error('unexpected end of file, tag id is incomplete');
-
$this->createOpenTag();
-
$this->makeTag();
return self::FINAL_STATE;
}
/**
- * @return HtmlTokenizer
- **/
- private function dumpEndTag()
+ * @return static
+ * @throws WrongArgumentException
+ */
+ private function dumpEndTag(): HtmlTokenizer
{
if (!$this->tagId) {
// >
$this->warning('empty end-tag, storing with empty id');
-
} elseif (!self::isValidId($this->tagId)) {
-
$this->error("end-tag id '{$this->tagId}' is invalid");
}
- $this->tag = SgmlEndTag::create()->
- setId(
- self::optionalLowercase($this->tagId, $this->lowercaseTags)
- );
-
+ $this->tag =
+ SgmlEndTag::create()->
+ setId(
+ self::optionalLowercase($this->tagId, $this->lowercaseTags)
+ );
$this->makeTag();
return $this;
}
- // END_TAG_STATE
- private function endTagState()
+ /**
+ * END_TAG_STATE
+ * @return int
+ * @throws WrongArgumentException
+ */
+ private function endTagState(): int
{
Assert::isNull($this->tag);
-
Assert::isTrue(
$this->tagId === null
|| $this->char == '>'
|| self::isSpacerChar($this->char)
);
-
Assert::isNull($this->attrName);
Assert::isNull($this->attrValue);
-
Assert::isNull($this->insideQuote);
$eatingGarbage = false;
while ($this->char !== null) {
-
if ($this->char == '>') {
- $this->dumpEndTag();
-
- $this->getNextChar();
+ $this
+ ->dumpEndTag()
+ ->getNextChar();
return self::INITIAL_STATE;
-
} elseif ($eatingGarbage) {
-
$this->getNextChar();
continue;
-
} elseif (self::isSpacerChar($this->char)) {
// most browsers parse end-tag until next '>' char
$eatingGarbage = true;
-
$this->getNextChar();
continue;
@@ -698,66 +764,59 @@ private function endTagState()
// ... [end-of-file], error("unexpected end of file, end-tag is incomplete");
-
- $this->dumpEndTag();
+ $this
+ ->error("unexpected end of file, end-tag is incomplete")
+ ->dumpEndTag();
return self::FINAL_STATE;
}
- // INSIDE_TAG_STATE
- private function insideTagState()
+ /**
+ * INSIDE_TAG_STATE
+ * @return int
+ * @throws WrongArgumentException
+ */
+ private function insideTagState(): int
{
Assert::isNull($this->tagId);
-
Assert::isNull($this->attrName);
Assert::isNull($this->attrValue);
-
Assert::isNotNull($this->tag);
- Assert::isTrue($this->tag instanceof SgmlOpenTag);
-
+ Assert::isInstance($this->tag, SgmlOpenTag::class);
Assert::isNull($this->insideQuote);
while ($this->char !== null) {
-
if (self::isSpacerChar($this->char)) {
- $this->getNextChar();
+ $this->getNextChar();
} elseif ($this->char == '>') {
//
- $this->makeTag();
-
- $this->getNextChar();
+ $this
+ ->makeTag()
+ ->getNextChar();
return self::INITIAL_STATE;
-
} elseif ($this->char == '=') {
// most browsers' behaviour
- $this->error(
- 'unexpected equal sign, attr name considered empty'
- );
-
- $this->getNextChar();
+ $this
+ ->error('unexpected equal sign, attr name considered empty')
+ ->getNextChar();
// call?
return self::ATTR_VALUE_STATE;
-
} else {
-
$char = $this->char;
-
$this->getNextChar();
if ($char == '/' && $this->char == '>') {
// ,
$this->tag->setEmpty(true);
-
- $this->makeTag();
-
- $this->getNextChar();
+ $this
+ ->makeTag()
+ ->getNextChar();
return self::INITIAL_STATE;
}
@@ -771,50 +830,49 @@ private function insideTagState()
// error('unexpected end of file, incomplete tag stored');
-
- $this->makeTag();
+ $this
+ ->error('unexpected end of file, incomplete tag stored')
+ ->makeTag();
return self::FINAL_STATE;
}
/**
- * @return SgmlOpenTag
- **/
- private function dumpAttribute()
+ * @return static
+ */
+ private function dumpAttribute(): HtmlTokenizer
{
if ($this->attrName) {
-
if (!self::isValidId($this->attrName))
$this->error("attribute name '{$this->attrName}' is invalid");
else
$this->attrName = strtolower($this->attrName);
-
}
- if ($this->attrValue === null || $this->attrValue === '')
+ if ($this->attrValue === null || $this->attrValue === '') {
$this->warning("empty value for attr == '{$this->attrName}'");
+ }
$this->tag->setAttribute($this->attrName, $this->attrValue);
-
$this->attrName = $this->attrValue = null;
return $this;
}
- // ATTR_NAME_STATE
- private function attrNameState()
+ /**
+ * ATTR_NAME_STATE
+ * @return int
+ * @throws WrongArgumentException
+ */
+ private function attrNameState(): int
{
Assert::isNotNull($this->tag);
- Assert::isTrue($this->tag instanceof SgmlOpenTag);
-
+ Assert::isInstance($this->tag,SgmlOpenTag::class);
Assert::isNotNull($this->attrName); // length == 1
Assert::isNull($this->attrValue);
-
Assert::isNull($this->insideQuote);
while ($this->char !== null) {
-
if (self::isSpacerChar($this->char)) {
// char == '>') {
//
- $this->dumpAttribute();
-
- $this->makeTag();
-
- $this->getNextChar();
+ $this
+ ->dumpAttribute()
+ ->makeTag()
+ ->getNextChar();
return self::INITIAL_STATE;
-
} elseif ($this->char == '=') {
// char;
$this->getNextChar();
@@ -855,12 +908,10 @@ private function attrNameState()
//
$this->tag->setEmpty(true);
-
- $this->dumpAttribute();
-
- $this->makeTag();
-
- $this->getNextChar();
+ $this
+ ->dumpAttribute()
+ ->makeTag()
+ ->getNextChar();
return self::INITIAL_STATE;
}
@@ -870,37 +921,35 @@ private function attrNameState()
}
// dumpAttribute();
-
- $this->error('unexpected end of file, incomplete tag stored');
-
- $this->makeTag();
+ $this
+ ->dumpAttribute()
+ ->error('unexpected end of file, incomplete tag stored')
+ ->makeTag();
return self::FINAL_STATE;
}
- // WAITING_EQUAL_SIGN_STATE
- private function waitingEqualSignState()
+ /**
+ * WAITING_EQUAL_SIGN_STATE
+ * @return int
+ * @throws WrongArgumentException
+ */
+ private function waitingEqualSignState(): int
{
Assert::isNotNull($this->tag);
- Assert::isTrue($this->tag instanceof SgmlOpenTag);
+ Assert::isInstance($this->tag, SgmlOpenTag::class);
Assert::isNull($this->tagId);
Assert::isNotNull($this->attrName);
Assert::isNull($this->attrValue);
-
Assert::isNull($this->insideQuote);
while ($this->char !== null) {
-
if (self::isSpacerChar($this->char)) {
// getNextChar();
-
} elseif ($this->char == '=') {
-
$this->getNextChar();
// empty string, not null, to be sure that value needed
@@ -908,7 +957,6 @@ private function waitingEqualSignState()
// call?
return self::ATTR_VALUE_STATE;
-
} else {
//
@@ -920,22 +968,25 @@ private function waitingEqualSignState()
// dumpAttribute();
-
- $this->error('unexpected end of file, incomplete tag stored');
-
- $this->makeTag();
+ $this
+ ->dumpAttribute()
+ ->error('unexpected end of file, incomplete tag stored')
+ ->makeTag();
return self::FINAL_STATE;
}
- // ATTR_VALUE_STATE
- private function attrValueState()
+ /**
+ * ATTR_VALUE_STATE
+ * @return int
+ * @throws IOException
+ * @throws WrongArgumentException
+ */
+ private function attrValueState(): int
{
Assert::isNull($this->tagId);
-
Assert::isNotNull($this->tag);
- Assert::isTrue($this->tag instanceof SgmlOpenTag);
+ Assert::isInstance($this->tag, SgmlOpenTag::class);
while ($this->char !== null) {
@@ -957,14 +1008,12 @@ private function attrValueState()
} elseif (!$this->insideQuote && $this->char == '>') {
// ,
- $this->dumpAttribute();
-
- $this->makeTag();
-
- $this->getNextChar();
+ $this
+ ->dumpAttribute()
+ ->makeTag()
+ ->getNextChar();
return self::INITIAL_STATE;
-
} else {
if (
$this->char == '"' || $this->char == "'"
@@ -975,27 +1024,22 @@ private function attrValueState()
$this->insideQuote = $this->char;
$this->getNextChar();
-
- // a place to rollback if second quote will not be
- // found.
+ // a place to rollback if second quote will not be found.
$this->mark();
continue;
-
} elseif ($this->char == $this->insideQuote) {
// attr = "value", attr='value', attr='value>([^']*)
- $this->dumpAttribute();
-
- $this->getNextChar();
+ $this
+ ->dumpAttribute()
+ ->getNextChar();
if ($this->insideQuote == '>') {
$this->insideQuote = null;
-
$this->makeTag();
return self::INITIAL_STATE;
-
} else {
$this->insideQuote = null;
@@ -1017,16 +1061,14 @@ private function attrValueState()
// attrName}',"
- ." rolling back and searching '>'"
- );
-
+ $this
+ ->reset()
+ ->warning(
+ "unclosed quoted value for attr == '{$this->attrName}', rolling back and searching '>'"
+ );
$this->attrValue = null;
$this->insideQuote = '>';
@@ -1037,22 +1079,24 @@ private function attrValueState()
// dumpAttribute();
-
- $this->error('unexpected end of file, incomplete tag stored');
-
- $this->makeTag();
+ $this
+ ->dumpAttribute()
+ ->error('unexpected end of file, incomplete tag stored')
+ ->makeTag();
return self::FINAL_STATE;
}
- // INLINE_TAG_STATE:
- private function inlineTagState()
+ /**
+ * INLINE_TAG_STATE
+ * @return int
+ * @throws IOException
+ * @throws WrongArgumentException
+ */
+ private function inlineTagState(): int
{
// char === '>' || self::isSpacerChar($this->char)
- ) {
+ } elseif ($this->char === '>' || self::isSpacerChar($this->char)) {
// , dumpBuffer();
-
$this->tagId = $startTag;
return self::END_TAG_STATE;
}
// buffer .= $endTag.$this->char;
$this->getNextChar();
}
$this->dumpBuffer();
-
- $this->error(
- "end-tag for inline tag == '{$startTag}' not found"
- );
+ $this->error("end-tag for inline tag == '{$startTag}' not found");
return self::FINAL_STATE;
}
- // CDATA_STATE
- private function cdataState()
+ /**
+ * CDATA_STATE
+ * @return int
+ * @throws WrongArgumentException
+ */
+ private function cdataState(): int
{
Assert::isNull($this->tag);
Assert::isNull($this->tagId);
@@ -1131,7 +1171,6 @@ private function cdataState()
$this->makeTag();
if (!$this->substringFound) {
-
$this->error('unexpected end-of-file inside cdata tag');
return self::FINAL_STATE;
@@ -1140,7 +1179,12 @@ private function cdataState()
return self::INITIAL_STATE;
}
- private function getComment()
+ /**
+ * @return string
+ * @throws IOException
+ * @throws WrongArgumentException
+ */
+ private function getComment(): string
{
$this->mark();
@@ -1149,86 +1193,82 @@ private function getComment()
if (!$this->substringFound) {
$this->reset();
- $this->error(
- 'unexpected end-of-file inside comment tag,'
- ." trying to find '>'"
- );
+ $this->error("unexpected end-of-file inside comment tag, trying to find '>'");
$result = $this->getContentToSubstring('>');
- if (!$this->substringFound)
- $this->error(
- "end-tag '>' not found,"
- .' treating all remaining content as cdata'
- );
+ if (!$this->substringFound) {
+ $this->error("end-tag '>' not found, treating all remaining content as cdata");
+ }
}
return $result;
}
- // COMMENT_STATE
- private function commentState()
+ /**
+ * COMMENT_STATE
+ * @return int
+ * @throws IOException
+ * @throws WrongArgumentException
+ */
+ private function commentState(): int
{
Assert::isNull($this->tag);
Assert::isNull($this->tagId);
$content = $this->getComment();
-
- $this->tag =
- SgmlIgnoredTag::comment()->
- setCdata(
- Cdata::create()->setData($content)
- );
-
+ $this->tag = SgmlIgnoredTag::comment()
+ ->setCdata(Cdata::create()->setData($content));
$this->makeTag();
return self::INITIAL_STATE;
}
- // EXTERNAL_TAG_STATE:
- private function externalTagState()
+ /**
+ * EXTERNAL_TAG_STATE:
+ * @return int
+ * @throws IOException
+ * @throws WrongArgumentException
+ */
+ private function externalTagState(): int
{
Assert::isTrue($this->tag instanceof SgmlIgnoredTag);
$this->mark();
-
$content = $this->getContentToSubstring('?>');
if (!$this->substringFound) {
$this->reset();
- $this->error(
- 'unexpected end-of-file inside external tag,'
- ." trying to find '>'"
- );
-
+ $this->error("unexpected end-of-file inside external tag, trying to find '>'");
$content = $this->getContentToSubstring('>');
- if (!$this->substringFound)
- $this->error(
- "end-tag '>' not found,"
- .' treating all remaining content as cdata'
- );
+ if (!$this->substringFound) {
+ $this->error("end-tag '>' not found, treating all remaining content as cdata");
+ }
}
$this->tag->setCdata(Cdata::create()->setData($content));
-
$this->makeTag();
return self::INITIAL_STATE;
}
- // DOCTYPE_TAG_STATE:
- private function doctypeTagState()
+ /**
+ * DOCTYPE_TAG_STATE:
+ * @return int
+ * @throws WrongArgumentException
+ * @todo use DoctypeTag and parse it correctly as Opera does and Firefox does not.
+ */
+ private function doctypeTagState(): int
{
- // TODO: use DoctypeTag and parse it correctly as Opera does and
- // Firefox does not.
Assert::isTrue($this->tag instanceof SgmlIgnoredTag);
$content = $this->getContentToSubstring('>');
- if (!$this->substringFound)
+ if (!$this->substringFound) {
$this->error('unexpected end-of-file inside doctype tag');
+ }
$this->tag->setCdata(Cdata::create()->setData($content));
@@ -1239,14 +1279,16 @@ private function doctypeTagState()
/**
* using Knuth-Morris-Pratt algorithm.
- *
* If $substring not found, returns whole remaining content
- **/
- private function getContentToSubstring($substring, $ignoreCase = false)
+ * @param string $substring
+ * @param bool $ignoreCase
+ * @return string
+ */
+ private function getContentToSubstring(string $substring, bool $ignoreCase = false): string
{
$this->substringFound = false;
- $substringLength = strlen($substring);
+ $substringLength = mb_strlen($substring);
$prefixTable = array(1 => 0);
$buffer = $substring."\x00";
@@ -1254,21 +1296,19 @@ private function getContentToSubstring($substring, $ignoreCase = false)
while ($this->char !== null) {
- if ($i < $substringLength)
+ if ($i < $substringLength) {
$char = $buffer[$i + 1];
- else {
+ } else {
$char = $this->char;
$buffer .= $char;
$this->getNextChar();
}
$maxLength = $prefixTable[$i + 1];
-
$char = self::optionalLowercase($char, $ignoreCase);
while (
- self::optionalLowercase($buffer[$maxLength], $ignoreCase)
- !== $char
+ self::optionalLowercase($buffer[$maxLength], $ignoreCase) !== $char
&& $maxLength > 0
) {
$maxLength = $prefixTable[$maxLength];
@@ -1277,10 +1317,7 @@ private function getContentToSubstring($substring, $ignoreCase = false)
++$i;
$prefixTable[$i + 1] =
- (
- self::optionalLowercase($buffer[$maxLength], $ignoreCase)
- === $char
- )
+ self::optionalLowercase($buffer[$maxLength], $ignoreCase) === $char
? $maxLength + 1
: 0;
@@ -1294,17 +1331,17 @@ private function getContentToSubstring($substring, $ignoreCase = false)
}
}
- if (!$this->substringFound)
- return substr(
- $buffer, $substringLength + 1
- );
- else
- return substr(
- $buffer, $substringLength + 1, $i - 2 * $substringLength
- );
+ if (!$this->substringFound) {
+ return mb_substr($buffer, $substringLength + 1);
+ } else {
+ return mb_substr($buffer, $substringLength + 1, $i - 2 * $substringLength);
+ }
}
- private function getTextualPosition()
+ /**
+ * @return string
+ */
+ private function getTextualPosition(): string
{
return
"line {$this->line}, position {$this->linePosition}"
@@ -1316,25 +1353,26 @@ private function getTextualPosition()
}
/**
+ * @param string $message
* @return HtmlTokenizer
- **/
- private function warning($message)
+ */
+ private function warning(string $message): HtmlTokenizer
{
$this->errors[] =
- "warning at {$this->getTextualPosition()}: $message";
+ "warning at {$this->getTextualPosition()}: {$message}";
return $this;
}
/**
+ * @param string $message
* @return HtmlTokenizer
- **/
- private function error($message)
+ */
+ private function error(string $message): HtmlTokenizer
{
$this->errors[] =
"error at {$this->getTextualPosition()}: $message";
return $this;
}
-}
-?>
+}
\ No newline at end of file
diff --git a/src/Main/Markup/Html/SgmlEndTag.php b/src/Main/Markup/Html/SgmlEndTag.php
index 2d88bf5bd8..916ef2fc04 100644
--- a/src/Main/Markup/Html/SgmlEndTag.php
+++ b/src/Main/Markup/Html/SgmlEndTag.php
@@ -15,14 +15,4 @@
* @ingroup Html
* @ingroup Module
**/
-final class SgmlEndTag extends SgmlTag
-{
- /**
- * @return SgmlEndTag
- **/
- public static function create()
- {
- return new self;
- }
-}
-?>
\ No newline at end of file
+final class SgmlEndTag extends SgmlTag { /** */ }
\ No newline at end of file
diff --git a/src/Main/Markup/Html/SgmlIgnoredTag.php b/src/Main/Markup/Html/SgmlIgnoredTag.php
index 3a8df5cc75..06fd4c133d 100644
--- a/src/Main/Markup/Html/SgmlIgnoredTag.php
+++ b/src/Main/Markup/Html/SgmlIgnoredTag.php
@@ -16,29 +16,28 @@
**/
final class SgmlIgnoredTag extends SgmlTag
{
- private $cdata = null;
- private $endMark = null;
-
/**
- * @return SgmlIgnoredTag
- **/
- public static function create()
- {
- return new self;
- }
+ * @var Cdata|null
+ */
+ private ?Cdata $cdata = null;
+ /**
+ * @var string|null
+ */
+ private ?string $endMark = null;
/**
- * @return SgmlIgnoredTag
- **/
- public static function comment()
+ * @return static
+ */
+ public static function comment(): SgmlIgnoredTag
{
return self::create()->setId('!--')->setEndMark('--');
}
/**
- * @return SgmlIgnoredTag
- **/
- public function setCdata(Cdata $cdata)
+ * @param Cdata $cdata
+ * @return static
+ */
+ public function setCdata(Cdata $cdata): SgmlIgnoredTag
{
$this->cdata = $cdata;
@@ -46,36 +45,45 @@ public function setCdata(Cdata $cdata)
}
/**
- * @return Cdata
- **/
- public function getCdata()
+ * @return Cdata|null
+ */
+ public function getCdata(): ?Cdata
{
return $this->cdata;
}
/**
- * @return SgmlIgnoredTag
- **/
- public function setEndMark($endMark)
+ * @param string $endMark
+ * @return static
+ */
+ public function setEndMark(string $endMark): SgmlIgnoredTag
{
$this->endMark = $endMark;
return $this;
}
- public function getEndMark()
+ /**
+ * @return string|null
+ */
+ public function getEndMark(): ?string
{
return $this->endMark;
}
- public function isComment()
+ /**
+ * @return bool
+ */
+ public function isComment(): bool
{
return $this->getId() == '!--';
}
- public function isExternal()
+ /**
+ * @return bool
+ */
+ public function isExternal(): bool
{
return mb_substr($this->getId() ?? '', 0, 1) == '?';
}
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Main/Markup/Html/SgmlOpenTag.php b/src/Main/Markup/Html/SgmlOpenTag.php
index 3b34ae1e3e..381fee7941 100644
--- a/src/Main/Markup/Html/SgmlOpenTag.php
+++ b/src/Main/Markup/Html/SgmlOpenTag.php
@@ -11,7 +11,6 @@
namespace OnPHP\Main\Markup\Html;
-use OnPHP\Core\Base\Assert;
use OnPHP\Core\Exception\WrongArgumentException;
/**
@@ -19,93 +18,115 @@
**/
final class SgmlOpenTag extends SgmlTag
{
- private $attributes = array();
- private $empty = false;
-
/**
- * @return SgmlOpenTag
- **/
- public static function create()
- {
- return new self;
- }
+ * @var array
+ */
+ private array $attributes = [];
+ /**
+ * @var bool
+ */
+ private bool $empty = false;
/**
- * @return SgmlOpenTag
- **/
- public function setEmpty($isEmpty)
+ * @param bool $isEmpty
+ * @return static
+ */
+ public function setEmpty(bool $isEmpty): SgmlOpenTag
{
- Assert::isBoolean($isEmpty);
-
$this->empty = $isEmpty;
return $this;
}
- public function isEmpty()
+ /**
+ * @return bool
+ */
+ public function isEmpty(): bool
{
return $this->empty;
}
/**
- * @return SgmlOpenTag
- **/
- public function setAttribute($name, $value)
+ * @param string $name
+ * @param mixed $value
+ * @return static
+ * @throws WrongArgumentException
+ */
+ public function setAttribute(string $name, $value = null): SgmlOpenTag
{
+ if ($this->hasAttribute($name)) {
+ throw new WrongArgumentException("attribute '{$name}' already exist");
+ }
+
$this->attributes[$name] = $value;
return $this;
}
- public function hasAttribute($name)
+ /**
+ * @param string $name
+ * @return bool
+ */
+ public function hasAttribute(string $name): bool
{
- $name = strtolower($name);
-
- return isset($this->attributes[$name]);
+ return in_array(
+ mb_strtolower($name),
+ array_map('mb_strtolower', array_keys($this->attributes))
+ );
}
- public function getAttribute($name)
+ /**
+ * @param string $name
+ * @return mixed
+ * @throws WrongArgumentException
+ */
+ public function getAttribute(string $name)
{
- $name = strtolower($name);
+ $name = mb_strtolower($name);
- if (!isset($this->attributes[$name]))
- throw new WrongArgumentException(
- "attribute '{$name}' does not exist"
- );
+ foreach($this->attributes as $attributeName => $value) {
+ if (mb_strtolower($attributeName) == $name) {
+ return $value;
+ }
+ }
- return $this->attributes[$name];
+ throw new WrongArgumentException("attribute '{$name}' does not exist");
}
/**
- * @return SgmlOpenTag
- **/
- public function dropAttribute($name)
+ * @param string $name
+ * @return static
+ * @throws WrongArgumentException
+ */
+ public function dropAttribute(string $name): SgmlOpenTag
{
- $name = strtolower($name);
+ $name = mb_strtolower($name);
- if (!isset($this->attributes[$name]))
- throw new WrongArgumentException(
- "attribute '{$name}' does not exist"
- );
+ foreach($this->attributes as $attributeName => $value) {
+ if (mb_strtolower($attributeName) == $name) {
+ unset($this->attributes[$attributeName]);
+ return $this;
+ }
+ }
- unset($this->attributes[$name]);
-
- return $this;
+ throw new WrongArgumentException("attribute '{$name}' does not exist");
}
- public function getAttributesList()
+ /**
+ * @return array
+ */
+ public function getAttributesList(): array
{
return $this->attributes;
}
/**
- * @return SgmlOpenTag
- **/
- public function dropAttributesList()
+ * @return static
+ */
+ public function dropAttributesList(): SgmlOpenTag
{
$this->attributes = array();
return $this;
}
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Main/Markup/Html/SgmlTag.php b/src/Main/Markup/Html/SgmlTag.php
index 5988b48705..f8327287e2 100644
--- a/src/Main/Markup/Html/SgmlTag.php
+++ b/src/Main/Markup/Html/SgmlTag.php
@@ -17,21 +17,27 @@
**/
abstract class SgmlTag extends SgmlToken
{
- private $id = null;
+ /**
+ * @var string|null
+ */
+ private ?string $id = null;
/**
- * @return SgmlTag
- **/
- public function setId($id)
+ * @param string|null $id
+ * @return static
+ */
+ public function setId(?string $id): SgmlTag
{
$this->id = $id;
return $this;
}
- public function getId()
+ /**
+ * @return string|null
+ */
+ public function getId(): ?string
{
return $this->id;
}
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Main/Markup/Html/SgmlToken.php b/src/Main/Markup/Html/SgmlToken.php
index 901cad2b92..6af024f182 100644
--- a/src/Main/Markup/Html/SgmlToken.php
+++ b/src/Main/Markup/Html/SgmlToken.php
@@ -17,21 +17,35 @@
**/
class SgmlToken
{
- private $value = null;
+ /**
+ * @var string|null
+ */
+ private ?string $value = null;
+
+ /**
+ * @return static
+ */
+ public static function create(): SgmlToken
+ {
+ return new static;
+ }
/**
- * @return SgmlToken
- **/
- public function setValue($value)
+ * @param string|null $value
+ * @return static
+ */
+ public function setValue(?string $value): SgmlToken
{
$this->value = $value;
return $this;
}
- public function getValue()
+ /**
+ * @return string|null
+ */
+ public function getValue(): ?string
{
return $this->value;
}
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Main/Message/Specification/MessageQueue.php b/src/Main/Message/Specification/MessageQueue.php
index 6e0d8ecb1f..c3fc3658dc 100644
--- a/src/Main/Message/Specification/MessageQueue.php
+++ b/src/Main/Message/Specification/MessageQueue.php
@@ -11,6 +11,4 @@
namespace OnPHP\Main\Message\Specification;
-interface MessageQueue { /* nop */ }
-
-?>
\ No newline at end of file
+interface MessageQueue { /* nop */ }
\ No newline at end of file
diff --git a/src/Main/Message/Specification/MessageQueueReceiver.php b/src/Main/Message/Specification/MessageQueueReceiver.php
index 88eb7d0ae2..e2489f6fcd 100644
--- a/src/Main/Message/Specification/MessageQueueReceiver.php
+++ b/src/Main/Message/Specification/MessageQueueReceiver.php
@@ -16,11 +16,10 @@ interface MessageQueueReceiver
/**
* @return Message
**/
- public function receive($uTimeout = null);
+ public function receive(int $uTimeout = null);
/**
* @return MessageQueue
**/
public function getQueue();
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Main/Message/TextFileQueue.php b/src/Main/Message/TextFileQueue.php
index fde6ee412e..75605098dd 100644
--- a/src/Main/Message/TextFileQueue.php
+++ b/src/Main/Message/TextFileQueue.php
@@ -15,36 +15,58 @@
class TextFileQueue implements MessageQueue
{
- private $fileName = null;
- private $offset = null;
+ /**
+ * @var string|null
+ */
+ private ?string $fileName = null;
+ /**
+ * @var int|null
+ */
+ private ?int $offset = 0;
- public static function create()
+ /**
+ * @return static
+ */
+ public static function create(): TextFileQueue
{
- return new self;
+ return new static;
}
- public function setFileName($fileName)
+ /**
+ * @param string $fileName
+ * @return static
+ */
+ public function setFileName(?string $fileName): TextFileQueue
{
$this->fileName = $fileName;
return $this;
}
- public function getFileName()
+ /**
+ * @return string|null
+ */
+ public function getFileName(): ?string
{
return $this->fileName;
}
- public function setOffset($offset)
+ /**
+ * @param int $offset
+ * @return static
+ */
+ public function setOffset(int $offset): TextFileQueue
{
$this->offset = $offset;
return $this;
}
- public function getOffset()
+ /**
+ * @return int
+ */
+ public function getOffset(): int
{
return $this->offset;
}
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Main/Message/TextFileReceiver.php b/src/Main/Message/TextFileReceiver.php
index f93dda818d..0868e18b96 100644
--- a/src/Main/Message/TextFileReceiver.php
+++ b/src/Main/Message/TextFileReceiver.php
@@ -13,29 +13,34 @@
use OnPHP\Core\Base\Assert;
use OnPHP\Core\Base\Timestamp;
+use OnPHP\Core\Exception\IOException;
+use OnPHP\Core\Exception\WrongArgumentException;
use OnPHP\Core\Exception\WrongStateException;
use OnPHP\Main\Message\Specification\Message;
use OnPHP\Main\Message\Specification\MessageQueue;
use OnPHP\Main\Message\Specification\MessageQueueReceiver;
use OnPHP\Main\Util\IO\FileInputStream;
+use OnPHP\Main\Util\IO\InputStream;
final class TextFileReceiver implements MessageQueueReceiver
{
- private $queue = null;
- private $stream = null;
+ private ?MessageQueue $queue = null;
+ private ?FileInputStream $stream = null;
/**
- * @return TextFileReceiver
- **/
- public static function create()
+ * @return static
+ */
+ public static function create(): TextFileReceiver
{
return new self;
}
/**
- * @return TextFileReceiver
- **/
- public function setQueue(MessageQueue $queue)
+ * @param MessageQueue $queue
+ * @return static
+ * @throws WrongArgumentException
+ */
+ public function setQueue(MessageQueue $queue): TextFileReceiver
{
Assert::isInstance($queue, TextFileQueue::class);
@@ -45,61 +50,70 @@ public function setQueue(MessageQueue $queue)
}
/**
- * @return MessageQueue
- **/
- public function getQueue()
+ * @return MessageQueue|null
+ */
+ public function getQueue(): ?MessageQueue
{
return $this->queue;
}
/**
- * @return Message
- **/
- public function receive($uTimeout = null)
+ * @param int|null $uTimeout
+ * @return Message|null
+ * @throws WrongArgumentException
+ * @throws WrongStateException
+ * @throws IOException
+ */
+ public function receive(int $uTimeout = null): ?Message
{
- if (!$this->queue)
+ if (!$this->queue instanceof MessageQueue) {
throw new WrongStateException('you must set the queue first');
+ }
- if ($uTimeout && $this->getStream()->isEof())
+ if ($uTimeout && $this->getStream()->isEof()) {
usleep($uTimeout);
+ }
$string = $this->getStream()->readString();
- if (!$string && $this->getStream()->isEof())
+ if (!$string && $this->getStream()->isEof()) {
return null;
+ }
- $this->getQueue()->setOffset($this->getStream()->getOffset());
+ if (($streamOffset = $this->getStream()->getOffset()) !== false) {
+ $this->getQueue()->setOffset($streamOffset);
+ }
$string = rtrim($string, PHP_EOL);
-
$chunks = preg_split("/\t/", $string, 2);
- $time = isset($chunks[0]) ? $chunks[0] : null;
- $text = isset($chunks[1]) ? $chunks[1] : null;
+ $time = $chunks[0] ?? null;
+ $text = $chunks[1] ?? null;
Assert::isNotNull($time);
- $result = TextMessage::create(Timestamp::create($time))->
- setText($text);
+ $result = TextMessage::create(Timestamp::create($time))
+ ->setText($text);
return $result;
}
/**
- * @return FileInputStream
- **/
- private function getStream()
+ * @return FileInputStream|null
+ * @throws IOException
+ * @throws WrongArgumentException
+ */
+ private function getStream(): ?FileInputStream
{
- if (!$this->stream) {
+ if (!$this->stream instanceof InputStream) {
Assert::isNotNull($this->queue->getFileName());
- $this->stream = FileInputStream::create(
- $this->queue->getFileName()
- )->
- seek($this->queue->getOffset());
+ $this->stream =
+ FileInputStream::create(
+ $this->queue->getFileName()
+ )->seek($this->queue->getOffset());
}
return $this->stream;
}
-}
-?>
+}
\ No newline at end of file
diff --git a/src/Main/Util/IO/BufferedInputStream.php b/src/Main/Util/IO/BufferedInputStream.php
index 115db6698f..d9d5a9ebfe 100644
--- a/src/Main/Util/IO/BufferedInputStream.php
+++ b/src/Main/Util/IO/BufferedInputStream.php
@@ -16,10 +16,10 @@
**/
final class BufferedInputStream extends InputStream
{
- private $runAheadBytes = 0;
+ private int $runAheadBytes = 0;
- private $in = null;
- private $closed = false;
+ private InputStream $in;
+ private bool $closed = false;
private $buffer = null;
private $bufferLength = 0;
@@ -33,37 +33,44 @@ public function __construct(InputStream $in)
}
/**
- * @return BufferedInputStream
- **/
- public static function create(InputStream $in)
+ * @param InputStream $in
+ * @return static
+ */
+ public static function create(InputStream $in): BufferedInputStream
{
return new self($in);
}
/**
- * @return BufferedInputStream
- **/
- public function close()
+ * @return static
+ */
+ public function close(): BufferedInputStream
{
$this->closed = true;
return $this;
}
- public function isEof()
+ /**
+ * @return bool
+ */
+ public function isEof(): bool
{
return $this->in->isEof();
}
- public function markSupported()
+ /**
+ * @return bool
+ */
+ public function markSupported(): bool
{
return true;
}
/**
- * @return BufferedInputStream
- **/
- public function mark()
+ * @return static
+ */
+ public function mark(): BufferedInputStream
{
$this->markPosition = $this->position;
@@ -71,43 +78,52 @@ public function mark()
}
/**
- * @return BufferedInputStream
- **/
- public function reset()
+ * @return static
+ */
+ public function reset(): BufferedInputStream
{
$this->position = $this->markPosition;
return $this;
}
- public function available()
+ /**
+ * @return int
+ */
+ public function available(): int
{
- if ($this->closed)
+ if ($this->closed) {
return 0;
+ }
return ($this->bufferLength - $this->position);
}
/**
- * @return BufferedInputStream
- **/
- public function setRunAheadBytes($runAheadBytes)
+ * @param int $runAheadBytes
+ * @return static
+ */
+ public function setRunAheadBytes(int $runAheadBytes): BufferedInputStream
{
$this->runAheadBytes = $runAheadBytes;
return $this;
}
- public function read($count)
+ /**
+ * @param int $length
+ * @return string|null
+ */
+ public function read(int $length): ?string
{
if ($this->closed)
return null;
- $remainingCount = $count;
+ $remainingCount = $length;
$availableCount = $this->available();
if ($remainingCount <= $availableCount)
- $readFromBuffer = $count;
+ $readFromBuffer = $length;
else
$readFromBuffer = $availableCount;
@@ -147,5 +163,4 @@ public function read($count)
return $result;
}
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Main/Util/IO/FileInputStream.php b/src/Main/Util/IO/FileInputStream.php
index 5a65c971aa..df8a733011 100644
--- a/src/Main/Util/IO/FileInputStream.php
+++ b/src/Main/Util/IO/FileInputStream.php
@@ -19,18 +19,24 @@
**/
final class FileInputStream extends InputStream
{
- private $fd = null;
-
- private $mark = null;
+ /**
+ * @var resource
+ */
+ private $fd;
+ private $mark = null;
+ /**
+ * @param resource $nameOrFd
+ * @throws IOException
+ */
public function __construct($nameOrFd)
{
if (is_resource($nameOrFd)) {
- if (get_resource_type($nameOrFd) !== 'stream')
+ if (get_resource_type($nameOrFd) !== 'stream') {
throw new IOException('not a file resource');
+ }
$this->fd = $nameOrFd;
-
} else {
try {
$this->fd = fopen($nameOrFd, 'rb');
@@ -50,81 +56,112 @@ public function __destruct()
}
/**
- * @return FileInputStream
- **/
- public static function create($nameOrFd)
+ * @param resource $nameOrFd
+ * @return static
+ * @throws IOException
+ */
+ public static function create($nameOrFd): FileInputStream
{
return new self($nameOrFd);
}
- public function isEof()
+ /**
+ * @return bool
+ */
+ public function isEof(): bool
{
return feof($this->fd);
}
/**
- * @return FileInputStream
- **/
- public function mark()
+ * @return static
+ */
+ public function mark(): FileInputStream
{
$this->mark = $this->getOffset();
return $this;
}
+ /**
+ * @return false|int
+ */
public function getOffset()
{
return ftell($this->fd);
}
- public function markSupported()
+ /**
+ * @return bool
+ */
+ public function markSupported(): bool
{
return true;
}
/**
- * @return FileInputStream
- **/
- public function reset()
+ * @return static
+ * @throws IOException
+ */
+ public function reset(): FileInputStream
{
return $this->seek($this->mark);
}
/**
- * @return FileInputStream
- **/
- public function seek($offset)
+ * @param int $offset
+ * @return static
+ * @throws IOException
+ */
+ public function seek(int $offset): FileInputStream
{
- if (fseek($this->fd, $offset) < 0)
- throw new IOException(
- 'mark has been invalidated'
- );
+ if (fseek($this->fd, $offset) < 0) {
+ throw new IOException('mark has been invalidated');
+ }
return $this;
}
/**
- * @return FileInputStream
- **/
- public function close()
+ * @return static
+ * @throws IOException
+ */
+ public function close(): FileInputStream
{
- if (!fclose($this->fd))
+ if (!fclose($this->fd)) {
throw new IOException('failed to close the file');
+ }
return $this;
}
- public function read($length)
+ /**
+ * @param int $length
+ * @return string|null
+ * @throws IOException
+ */
+ public function read(int $length): ?string
{
return $this->realRead($length);
}
- public function readString($length = null)
+ /**
+ * @param int|null $length
+ * @return string|null
+ * @throws IOException
+ */
+ public function readString(int $length = null): ?string
{
return $this->realRead($length, true);
}
- public function realRead($length, $string = false)
+ /**
+ * @param int $length
+ * @param bool $string
+ * @return string|null
+ * @throws IOException
+ */
+ public function realRead(int $length = null, bool $string = false): ?string
{
$result = $string
? (
@@ -134,16 +171,18 @@ public function realRead($length, $string = false)
)
: fread($this->fd, $length);
- if ($string && $result === false && feof($this->fd))
+ if ($string && $result === false && feof($this->fd)) {
$result = null; // fgets returns false on eof
+ }
- if ($result === false)
+ if ($result === false) {
throw new IOException('failed to read from file');
+ }
- if ($result === '')
+ if ($result === '') {
$result = null; // eof
+ }
return $result;
}
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Main/Util/IO/InputStream.php b/src/Main/Util/IO/InputStream.php
index c31fa3566c..e0b451abad 100644
--- a/src/Main/Util/IO/InputStream.php
+++ b/src/Main/Util/IO/InputStream.php
@@ -31,51 +31,70 @@ abstract class InputStream
*
* It is abnormal state. Maybe you should use some kind of
* non-blocking channels instead?
- *
- **/
- abstract public function read($length);
- abstract public function isEof();
+ *
+ * @param int $length
+ * @return string|null
+ */
+ abstract public function read(int $length): ?string;
+
+ /**
+ * @return bool
+ */
+ abstract public function isEof(): bool;
/**
- * @return InputStream
- **/
- public function mark()
+ * @return static
+ */
+ public function mark(): InputStream
{
/* nop */
return $this;
}
- public function markSupported()
+ /**
+ * @return bool
+ */
+ public function markSupported(): bool
{
return false;
}
- public function reset()
+ /**
+ * @return static
+ * @throws IOException
+ */
+ public function reset(): InputStream
{
throw new IOException(
'mark has been invalidated'
);
}
- public function skip($count)
+ /**
+ * @param int $count
+ * @return int
+ */
+ public function skip(int $count): int
{
- return strlen($this->read($count));
+ return mb_strlen($this->read($count));
}
- public function available()
+ /**
+ * @return int
+ */
+ public function available(): int
{
return 0;
}
/**
- * @return InputStream
- **/
- public function close()
+ * @return static
+ */
+ public function close(): InputStream
{
/* nop */
return $this;
}
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Main/Util/IO/SocketInputStream.php b/src/Main/Util/IO/SocketInputStream.php
index d624c847ad..d2aebaa144 100644
--- a/src/Main/Util/IO/SocketInputStream.php
+++ b/src/Main/Util/IO/SocketInputStream.php
@@ -30,20 +30,31 @@ final class SocketInputStream extends InputStream
**/
const READ_ATTEMPTS = 15; // should be enough for everyone (C)
- private $socket = null;
- private $eof = false;
+ private Socket $socket;
+ private bool $eof = false;
+ /**
+ * @param Socket $socket
+ */
public function __construct(Socket $socket)
{
$this->socket = $socket;
}
- public function isEof()
+ /**
+ * @return bool
+ */
+ public function isEof(): bool
{
return $this->eof;
}
- public function read($length)
+ /**
+ * @param int $length
+ * @return string|null
+ * @throws IOException
+ */
+ public function read(int $length): ?string
{
if ($length == 0 || $this->eof)
return null;
@@ -87,5 +98,4 @@ public function read($length)
return $result;
}
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/Main/Util/IO/StringInputStream.php b/src/Main/Util/IO/StringInputStream.php
index bc9922a5d4..9cc26a6441 100644
--- a/src/Main/Util/IO/StringInputStream.php
+++ b/src/Main/Util/IO/StringInputStream.php
@@ -18,52 +18,60 @@
**/
final class StringInputStream extends InputStream
{
- private $string = null;
- private $length = null;
+ private string $string;
+ private ?int $length = null;
- private $position = 0;
- private $mark = 0;
+ private int $position = 0;
+ private int $mark = 0;
- public function __construct($string)
+ /**
+ * @param string $string
+ */
+ public function __construct(string $string)
{
- Assert::isString($string);
-
$this->string = $string;
$this->length = strlen($string);
}
/**
- * @return StringInputStream
- **/
- public static function create($string)
+ * @param string $string
+ * @return self
+ */
+ public static function create(string $string): StringInputStream
{
return new self($string);
}
- public function isEof()
+ /**
+ * @return bool
+ */
+ public function isEof(): bool
{
return ($this->position >= $this->length);
}
/**
- * @return StringInputStream
- **/
- public function mark()
+ * @return static
+ */
+ public function mark(): StringInputStream
{
$this->mark = $this->position;
return $this;
}
- public function markSupported()
+ /**
+ * @return bool
+ */
+ public function markSupported():bool
{
return true;
}
/**
- * @return StringInputStream
- **/
- public function reset()
+ * @return static
+ */
+ public function reset(): StringInputStream
{
$this->position = $this->mark;
@@ -71,16 +79,20 @@ public function reset()
}
/**
- * @return StringInputStream
- **/
- public function close()
+ * @return static
+ */
+ public function close(): StringInputStream
{
$this->string = null;
return $this;
}
- public function read($count)
+ /**
+ * @param int $count
+ * @return string|null
+ */
+ public function read(int $count): ?string
{
if (!$this->string || $this->isEof())
return null;
@@ -88,12 +100,11 @@ public function read($count)
if ($count == 1) {
$result = $this->string[(int)$this->position];
} else {
- $result = substr($this->string, $this->position, $count);
+ $result = mb_substr($this->string, $this->position, $count);
}
$this->position += $count;
return $result;
}
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/tests/Main/Markup/CdataTest.php b/tests/Main/Markup/CdataTest.php
new file mode 100644
index 0000000000..c3bbf89c26
--- /dev/null
+++ b/tests/Main/Markup/CdataTest.php
@@ -0,0 +1,51 @@
+assertInstanceOf(Cdata::class, $tag);
+ }
+
+ public function testStrict()
+ {
+ $tag = Cdata::create();
+ $this->assertFalse($tag->isStrict());
+
+ $this->assertInstanceOf(Cdata::class, $tag->setStrict(true));
+ $this->assertTrue($tag->isStrict());
+ }
+
+ public function testData()
+ {
+ $tag = Cdata::create();
+ $this->assertNull($tag->getData());
+ $this->assertNull($tag->getRawData());
+
+ $data = 'test data content';
+ $tag->setData($data);
+ $this->assertEquals($data, $tag->getRawData());
+ $this->assertEquals($data, $tag->getData());
+ $tag->setStrict(true);
+ $this->assertEquals($data, $tag->getRawData());
+ $this->assertEquals(
+ Cdata::CDATA_STRICT_START . $data . Cdata::CDATA_STRICT_END,
+ $tag->getData()
+ );
+ }
+}
\ No newline at end of file
diff --git a/tests/Main/Markup/HtmlAssemblerTest.php b/tests/Main/Markup/HtmlAssemblerTest.php
new file mode 100644
index 0000000000..d8336bb47a
--- /dev/null
+++ b/tests/Main/Markup/HtmlAssemblerTest.php
@@ -0,0 +1,335 @@
+assertNull($html->getHtml());
+ $this->assertEmpty($this->getObjectProperty($html, 'tags'));
+
+ $input = [
+ SgmlOpenTag::create()->setId('div'),
+ Cdata::create()->setData('test text'),
+ SgmlEndTag::create()->setId('div')
+ ];
+ $html = new HtmlAssembler($input);
+ $tags = $this->getObjectProperty($html, 'tags');
+ $this->assertCount(3, $tags);
+ $this->assertEquals($input, $tags);
+
+ $input[] = 'test string';
+ $this->expectException(WrongArgumentException::class);
+ new HtmlAssembler($input);
+ }
+
+ public function testMakeTag()
+ {
+ $cdata = Cdata::create()->setData('test-data');
+ $this->assertEquals($cdata->getData(), HtmlAssembler::makeTag($cdata));
+ $cdata->setStrict(true);
+ $this->assertEquals($cdata->getData(), HtmlAssembler::makeTag($cdata));
+
+ try {
+ HtmlAssembler::makeTag(SgmlIgnoredTag::create());
+ $this->fail('expected WrongArgumentException exception');
+ } catch(\Throwable $exception) {
+ $this->assertInstanceOf(WrongArgumentException::class, $exception);
+ }
+ $this->assertEquals(
+ '',
+ HtmlAssembler::makeTag(SgmlIgnoredTag::comment())
+ );
+ $this->assertEquals(
+ '',
+ HtmlAssembler::makeTag(
+ SgmlIgnoredTag::comment()->setCdata(Cdata::create()->setData('comment'))
+ )
+ );
+ $cdata = Cdata::create()->setData('comment')->setStrict(true);
+ $this->assertEquals(
+ '',
+ HtmlAssembler::makeTag(
+ SgmlIgnoredTag::comment()->setCdata($cdata)
+ )
+ );
+ $cdata = Cdata::create()->setData(' /* PHP code */ ');
+ $this->assertEquals(
+ 'getData().'?>',
+ HtmlAssembler::makeTag(
+ SgmlIgnoredTag::create()->setId('?php')->setEndMark('?')->setCdata($cdata)
+ )
+ );
+
+ try {
+ HtmlAssembler::makeTag(SgmlOpenTag::create());
+ $this->fail('expected WrongArgumentException exception');
+ } catch(\Throwable $exception) {
+ $this->assertInstanceOf(WrongArgumentException::class, $exception);
+ }
+ $this->assertEquals(
+ '',
+ HtmlAssembler::makeTag(SgmlOpenTag::create()->setId('div'))
+ );
+ $this->assertEquals(
+ '
',
+ HtmlAssembler::makeTag(SgmlOpenTag::create()->setId('div')->setEmpty(true))
+ );
+ $this->assertEquals(
+ '
',
+ HtmlAssembler::makeTag(
+ SgmlOpenTag::create()->setId('div')->setEmpty(true)->setAttribute('class', 'test')
+ )
+ );
+ $this->assertEquals(
+ '
',
+ HtmlAssembler::makeTag(
+ SgmlOpenTag::create()
+ ->setId('div')->setEmpty(true)
+ ->setAttribute('class', 'test')
+ ->setAttribute('required')
+ )
+ );
+
+ try {
+ HtmlAssembler::makeTag(SgmlEndTag::create());
+ $this->fail('expected WrongArgumentException exception');
+ } catch(\Throwable $exception) {
+ $this->assertInstanceOf(WrongArgumentException::class, $exception);
+ }
+ $this->assertEquals(
+ '
',
+ HtmlAssembler::makeTag(SgmlEndTag::create()->setId('div'))
+ );
+
+ $this->expectException(WrongArgumentException::class);
+ HtmlAssembler::makeTag(SgmlTag::create());
+ }
+
+ public function testMakeDomNode()
+ {
+ $doc = new \DOMDocument("1.0");
+
+ try {
+ HtmlAssembler::makeDomNode($doc);
+ $this->fail('expected UnimplementedFeatureException exception');
+ } catch(\Throwable $exception) {
+ $this->assertInstanceOf(UnimplementedFeatureException::class, $exception);
+ }
+
+ $node = $doc->appendChild($doc->createElement("div"));
+ $this->assertEquals('', HtmlAssembler::makeDomNode($node));
+
+ $node->setAttribute("data-test", null);
+ $this->assertEquals('', HtmlAssembler::makeDomNode($node));
+ $node->setAttribute('class', 'test');
+ $this->assertEquals('', HtmlAssembler::makeDomNode($node));
+
+ $node->appendChild($doc->createTextNode("test content"));
+ $this->assertEquals(
+ 'test content
',
+ HtmlAssembler::makeDomNode($node)
+ );
+
+ $em = $doc->createElement("em");
+ $em->appendChild($doc->createTextNode('test em'));
+ $node->appendChild($em);
+ $node->appendChild($doc->createTextNode("appendix"));
+ $this->assertEquals(
+ 'test content'
+ . 'test emappendix
',
+ HtmlAssembler::makeDomNode($node)
+ );
+
+ $node->appendChild($doc->createElement("img"));
+ $this->assertEquals(
+ 'test content'
+ . '
test emappendix
![]()
',
+ HtmlAssembler::makeDomNode($node)
+ );
+ }
+
+ public function testGetAttributes()
+ {
+ $this->assertEmpty(
+ $this->callObjectMethod(
+ HtmlAssembler::class,
+ 'getAttributes',
+ SgmlOpenTag::create()->setId('div')
+ )
+ );
+
+ $this->assertEquals(
+ 'data-test',
+ $this->callObjectMethod(
+ HtmlAssembler::class,
+ 'getAttributes',
+ SgmlOpenTag::create()->setId('div')->setAttribute('data-test')
+ )
+ );
+
+ $this->assertEquals(
+ 'class="test"',
+ $this->callObjectMethod(
+ HtmlAssembler::class,
+ 'getAttributes',
+ SgmlOpenTag::create()->setId('div')->setAttribute('class', 'test')
+ )
+ );
+
+ $this->assertEquals(
+ 'class="test" data-test',
+ $this->callObjectMethod(
+ HtmlAssembler::class,
+ 'getAttributes',
+ SgmlOpenTag::create()->setId('div')
+ ->setAttribute('class', 'test')
+ ->setAttribute('data-test')
+ )
+ );
+
+ $this->assertEquals(
+ 'class="test" data-test id=""id"',
+ $this->callObjectMethod(
+ HtmlAssembler::class,
+ 'getAttributes',
+ SgmlOpenTag::create()->setId('div')
+ ->setAttribute('class', 'test')
+ ->setAttribute('data-test')
+ ->setAttribute('id', '"id')
+ )
+ );
+ }
+
+ public function testGetDomAttributes()
+ {
+ $doc = new \DOMDocument("1.0");
+ $node = $doc->appendChild($doc->createElement("div"));
+
+ $this->assertEmpty(
+ $this->callObjectMethod(
+ HtmlAssembler::class,
+ 'getDomAttributes',
+ $node
+ )
+ );
+
+ $node->setAttribute("data-test", null);
+ $this->assertEquals(
+ 'data-test',
+ $this->callObjectMethod(
+ HtmlAssembler::class,
+ 'getDomAttributes',
+ $node
+ )
+ );
+
+ $node->setAttribute('class', 'test');
+ $this->assertEquals(
+ 'data-test class="test"',
+ $this->callObjectMethod(
+ HtmlAssembler::class,
+ 'getDomAttributes',
+ $node
+ )
+ );
+
+ $node->setAttribute('id', '"id');
+ $this->assertEquals(
+ 'data-test class="test" id=""id"',
+ $this->callObjectMethod(
+ HtmlAssembler::class,
+ 'getDomAttributes',
+ $node
+ )
+ );
+ }
+
+ public function testReadAndBuid()
+ {
+ $html = <<
+
+
+
+ Новая вкладка
+
+
+
+
+
+
+
+
+
+
+
+
+HTML;
+ $tags = [];
+ $reader = HtmlTokenizer::create(StringInputStream::create($html));
+ while(($tag = $reader->nextToken()) !== null) {
+ $tags[] = $tag;
+ }
+ $html = (new HtmlAssembler($tags))->getHtml();
+ /**
+ * Need one iteration, because original rules of quotes and
+ * writing tags may difference
+ */
+ $tags = [];
+ $reader = HtmlTokenizer::create(StringInputStream::create($html));
+ while(($tag = $reader->nextToken()) !== null) {
+ $tags[] = $tag;
+ }
+
+ $this->assertEquals($html, (new HtmlAssembler($tags))->getHtml());
+ }
+}
\ No newline at end of file
diff --git a/tests/Main/Markup/SgmlEndTagTest.php b/tests/Main/Markup/SgmlEndTagTest.php
new file mode 100644
index 0000000000..72b939aeea
--- /dev/null
+++ b/tests/Main/Markup/SgmlEndTagTest.php
@@ -0,0 +1,24 @@
+assertInstanceOf(SgmlEndTag::class, $tag);
+ }
+}
\ No newline at end of file
diff --git a/tests/Main/Markup/SgmlIgnoredTagTest.php b/tests/Main/Markup/SgmlIgnoredTagTest.php
new file mode 100644
index 0000000000..6e4eb4dcf9
--- /dev/null
+++ b/tests/Main/Markup/SgmlIgnoredTagTest.php
@@ -0,0 +1,72 @@
+assertInstanceOf(SgmlIgnoredTag::class, SgmlIgnoredTag::create());
+ }
+
+ public function testComment()
+ {
+ $tag = SgmlIgnoredTag::comment();
+
+ $this->assertInstanceOf(SgmlIgnoredTag::class, $tag);
+ $this->assertEquals('!--', $tag->getId());
+ $this->assertEquals('--', $tag->getEndMark());
+ $this->assertTrue($tag->isComment());
+ $tag->setId('!-');
+ $this->assertFalse($tag->isComment());
+ }
+
+ public function testCData()
+ {
+ $tag = SgmlIgnoredTag::comment();
+
+ $this->assertNull($tag->getCdata());
+ $tag->setCdata((new Cdata())->setData('test data'));
+ $this->assertInstanceOf(Cdata::class, $tag->getCdata());
+ $this->assertEquals('test data', $tag->getCdata()->getData());
+ }
+
+ public function testEndMark()
+ {
+ $tag = SgmlIgnoredTag::create();
+
+ $this->assertNull($tag->getEndMark());
+ $tag->setEndMark('test-end-mark');
+ $this->assertEquals('test-end-mark', $tag->getEndMark());
+ $tag->setEndMark('');
+ $this->assertEquals('', $tag->getEndMark());
+ }
+
+ public function testExternal()
+ {
+ $tag = SgmlIgnoredTag::create();
+
+ $this->assertFalse($tag->isExternal());
+ $tag->setId('!--');
+ $this->assertFalse($tag->isExternal());
+ $tag->setId('?id');
+ $this->assertTrue($tag->isExternal());
+ $tag->setId('?');
+ $this->assertTrue($tag->isExternal());
+ $tag->setId('');
+ $this->assertFalse($tag->isExternal());
+ }
+}
\ No newline at end of file
diff --git a/tests/Main/Markup/SgmlOpenTagTest.php b/tests/Main/Markup/SgmlOpenTagTest.php
new file mode 100644
index 0000000000..a4e7eca42c
--- /dev/null
+++ b/tests/Main/Markup/SgmlOpenTagTest.php
@@ -0,0 +1,121 @@
+assertTrue($reflectionClass->isFinal());
+
+ $tag = SgmlOpenTag::create();
+ $this->assertInstanceOf(SgmlOpenTag::class, $tag);
+ $this->assertEmpty($tag->getAttributesList());
+ $this->assertFalse($tag->isEmpty());
+
+ $tag = new SgmlOpenTag();
+ $this->assertInstanceOf(SgmlOpenTag::class, $tag);
+ $this->assertEmpty($tag->getAttributesList());
+ $this->assertFalse($tag->isEmpty());
+ }
+
+ public function testEmpty()
+ {
+ $tag = SgmlOpenTag::create();
+ $tag->setEmpty(true);
+ $this->assertTrue($tag->isEmpty());
+ $tag->setEmpty(false);
+ $this->assertFalse($tag->isEmpty());
+ }
+
+ public function testAttributes()
+ {
+ $tag = SgmlOpenTag::create();
+ $tag->setAttribute('class', 'active');
+ $this->assertCount(1, $tag->getAttributesList());
+ $this->assertTrue($tag->hasAttribute('class'));
+ $this->assertTrue($tag->hasAttribute('ClasS'));
+ $this->assertEquals('active', $tag->getAttribute('class'));
+ $this->assertEquals($tag->getAttribute('class'), $tag->getAttribute('CLASS'));
+
+ try {
+ $tag->setAttribute('CLASS', 'in-active');
+ $this->fail('excepted WrongArgumentException exception');
+ } catch (\Throwable $exception) {
+ $this->assertInstanceOf(WrongArgumentException::class, $exception);
+ }
+ $this->assertCount(1, $tag->getAttributesList());
+ $this->assertTrue($tag->hasAttribute('class'));
+ $this->assertTrue($tag->hasAttribute('ClasS'));
+ $this->assertEquals('active', $tag->getAttribute('class'));
+ $this->assertEquals($tag->getAttribute('class'), $tag->getAttribute('CLASS'));
+
+ $tag->setAttribute('data-test', 'in-active');
+ $this->assertTrue($tag->hasAttribute('data-test'));
+ $this->assertCount(2, $tag->getAttributesList());
+ $this->assertEquals('in-active', $tag->getAttribute('data-test'));
+ $this->assertEquals(
+ ['class', 'data-test'],
+ array_keys($tag->getAttributesList())
+ );
+ $this->assertEquals(
+ ['active', 'in-active'],
+ array_values($tag->getAttributesList())
+ );
+
+ $tag->dropAttribute('DATA-TEST');
+ $this->assertFalse($tag->hasAttribute('data-test'));
+ $this->assertCount(1, $tag->getAttributesList());
+ try {
+ $tag->getAttribute('data-test');
+ $this->fail('excepted WrongArgumentException exception');
+ } catch (\Throwable $exception) {
+ $this->assertInstanceOf(WrongArgumentException::class, $exception);
+ }
+ try {
+ $tag->dropAttribute('data-test');
+ $this->fail('excepted WrongArgumentException exception');
+ } catch (\Throwable $exception) {
+ $this->assertInstanceOf(WrongArgumentException::class, $exception);
+ }
+
+ $tag->setAttribute('data-test', 'in-active');
+ $this->assertTrue($tag->hasAttribute('DATA-TEST'));
+ $this->assertCount(2, $tag->getAttributesList());
+ $this->assertEquals('in-active', $tag->getAttribute('DATA-TEST'));
+ $tag->dropAttribute('data-test');
+ $this->assertFalse($tag->hasAttribute('data-test'));
+ $this->assertCount(1, $tag->getAttributesList());
+ $tag->setAttribute('data-test', 'in-active');
+ $this->assertCount(2, $tag->getAttributesList());
+ $tag->dropAttributesList();
+ $this->assertEmpty($tag->getAttributesList());
+ try {
+ $tag->getAttribute('data-test');
+ $this->fail('excepted WrongArgumentException exception');
+ } catch (\Throwable $exception) {
+ $this->assertInstanceOf(WrongArgumentException::class, $exception);
+ }
+ try {
+ $tag->getAttribute('class');
+ $this->fail('excepted WrongArgumentException exception');
+ } catch (\Throwable $exception) {
+ $this->assertInstanceOf(WrongArgumentException::class, $exception);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Main/Markup/SgmlTagTest.php b/tests/Main/Markup/SgmlTagTest.php
new file mode 100644
index 0000000000..d8a1c1dc94
--- /dev/null
+++ b/tests/Main/Markup/SgmlTagTest.php
@@ -0,0 +1,42 @@
+assertTrue($reflectionClass->isAbstract());
+
+ $tag = \OnPHP\Tests\TestEnvironment\SgmlTag::create();
+ $this->assertInstanceOf(SgmlTag::class, $tag);
+ $this->assertNull($tag->getId());
+
+ $tag = new \OnPHP\Tests\TestEnvironment\SgmlTag();
+ $this->assertInstanceOf(SgmlTag::class, $tag);
+ $this->assertNull($tag->getId());
+ }
+
+ public function testValue()
+ {
+ $tag =
+ \OnPHP\Tests\TestEnvironment\SgmlTag::create()
+ ->setId('p');
+ $this->assertEquals('p', $tag->getId());
+ $tag->setId('strong');
+ $this->assertEquals('strong', $tag->getId());
+ }
+}
\ No newline at end of file
diff --git a/tests/Main/Markup/SgmlTokenTest.php b/tests/Main/Markup/SgmlTokenTest.php
new file mode 100644
index 0000000000..92074a38dd
--- /dev/null
+++ b/tests/Main/Markup/SgmlTokenTest.php
@@ -0,0 +1,41 @@
+assertInstanceOf(SgmlToken::class, $token);
+ $this->assertNull($token->getValue());
+
+ $token = new SgmlToken();
+ $this->assertInstanceOf(SgmlToken::class, $token);
+ $this->assertNull($token->getValue());
+ }
+
+ public function testValue()
+ {
+ $token = SgmlToken::create();
+ $token->setValue('test');
+ $this->assertEquals('test', $token->getValue());
+ $token->setValue(null);
+ $this->assertNull($token->getValue());
+ $this->expectException(TypeError::class);
+ $token->setValue(function() { return true; });
+ }
+}
\ No newline at end of file
diff --git a/tests/TestEnvironment/SgmlTag.php b/tests/TestEnvironment/SgmlTag.php
new file mode 100644
index 0000000000..75d8dd3853
--- /dev/null
+++ b/tests/TestEnvironment/SgmlTag.php
@@ -0,0 +1,17 @@
+
\ No newline at end of file
+
+ /**
+ * @param object $object
+ * @param string $parameter
+ * @return mixed
+ * @throws \ReflectionException
+ */
+ protected function getObjectProperty(object $object, string $parameter)
+ {
+ $class = new ReflectionClass($object);
+ $property = $class->getProperty($parameter);
+ $property->setAccessible(true);
+
+ return $property->getValue($object);
+ }
+
+ protected function callObjectMethod($className, string $methodName, ...$args)
+ {
+ $class = new ReflectionClass($className);
+ $method = $class->getMethod($methodName);
+ $method->setAccessible(true);
+ return $method->invokeArgs(
+ is_object($className) ? $className : null,
+ $args
+ );
+ }
+}
\ No newline at end of file