finished inline text semantics tags through <time>

This commit is contained in:
Joby 2023-09-10 04:48:31 +00:00
parent 885975e3b4
commit 1a12390c71
44 changed files with 1739 additions and 0 deletions

View file

@ -0,0 +1,20 @@
<?php
namespace ByJoby\HTML\Helpers;
use Stringable;
/**
* Class for automatically serializing and unserializing text values (such as in
* attributes) between a text representation that can be rendered and an object
* representation that is easy to work with.
*
* Objects that implement this can be passed into an Attributes object and will
* be stored as their object form and automatically converted into strings at
* render time. Some tags will have specific requirements for particular
* implementations of this interface for particular attribute getters/setters.
*/
interface StringableValue extends Stringable
{
public static function fromString(string|Stringable|null $string): self|null;
}

View file

@ -0,0 +1,34 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <b> HTML element is used to draw the reader's attention to the element's
* contents, which are not otherwise granted special importance. This was
* formerly known as the Boldface element, and most browsers still draw the text
* in boldface. However, you should not use <b> for styling text or granting
* importance. If you wish to create boldface text, you should use the CSS
* font-weight property. If you wish to indicate an element is of special
* importance, you should use the <strong> element.
*
* Do not confuse the <b> element with the <strong>, <em>, or <mark> elements.
* The <strong> element represents text of certain importance, <em> puts some
* emphasis on the text and the <mark> element represents text of certain
* relevance. The <b> element doesn't convey such special semantic information;
* use it only when no others fit.
*
* It is a good practice to use the class attribute on the <b> element in order
* to convey additional semantic information as needed (for example <b
* class="lead"> for the first sentence in a paragraph). This makes it easier to
* manage multiple use cases of <b> if your stylistic needs change, without the
* need to change all of its uses in the HTML.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b
*/
class BTag extends AbstractContainerTag
{
const TAG = 'b';
}

View file

@ -0,0 +1,29 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <bdi> HTML element tells the browser's bidirectional algorithm to treat
* the text it contains in isolation from its surrounding text. It's
* particularly useful when a website dynamically inserts some text and doesn't
* know the directionality of the text being inserted.
*
* Bidirectional text is text that may contain both sequences of characters that
* are arranged left-to-right (LTR) and sequences of characters that are
* arranged right-to-left (RTL), such as an Arabic quotation embedded in an
* English string. Browsers implement the Unicode Bidirectional Algorithm to
* handle this. In this algorithm, characters are given an implicit
* directionality: for example, Latin characters are treated as LTR while Arabic
* characters are treated as RTL. Some other characters (such as spaces and some
* punctuation) are treated as neutral and are assigned directionality based on
* that of their surrounding characters.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi
*/
class BdiTag extends AbstractContainerTag
{
const TAG = 'bdi';
}

View file

@ -0,0 +1,17 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <bdo> HTML element overrides the current directionality of text, so that
* the text within is rendered in a different direction.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdo
*/
class BdoTag extends AbstractContainerTag
{
const TAG = 'bdo';
}

View file

@ -0,0 +1,16 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractTag;
/**
* The <br> HTML element produces a line break in text (carriage-return). It is useful for writing a poem or an address, where the division of lines is significant.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br
*/
class BrTag extends AbstractTag
{
const TAG = 'br';
}

View file

@ -0,0 +1,18 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <cite> HTML element is used to mark up the title of a cited creative
* work. The reference may be in an abbreviated form according to
* context-appropriate conventions related to citation metadata.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite
*/
class CiteTag extends AbstractContainerTag
{
const TAG = 'cite';
}

View file

@ -0,0 +1,18 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <code> HTML element displays its contents styled in a fashion intended to
* indicate that the text is a short fragment of computer code. By default, the
* content text is displayed using the user agent's default monospace font.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code
*/
class CodeTag extends AbstractContainerTag
{
const TAG = 'code';
}

View file

@ -0,0 +1,56 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
use Stringable;
/**
* The <data> HTML element links a given piece of content with a
* machine-readable translation. If the content is time- or date-related, the
* <time> element must be used.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data
*/
class DataTag extends AbstractContainerTag
{
const TAG = 'data';
/**
* This attribute specifies the machine-readable translation of the content
* of the element.
*
* @return null|string|Stringable
*/
public function value(): null|string|Stringable
{
return $this->attributes()->asString('value');
}
/**
* This attribute specifies the machine-readable translation of the content
* of the element.
*
* @param null|string|Stringable $value
* @return static
*/
public function setValue(null|string|Stringable $value): static
{
if ($value) $this->attributes()['value'] = $value;
else $this->unsetValue();
return $this;
}
/**
* This attribute specifies the machine-readable translation of the content
* of the element.
*
* @return static
*/
public function unsetValue(): static
{
unset($this->attributes()['value']);
return $this;
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
use Stringable;
/**
* The <dfn> HTML element is used to indicate the term being defined within the
* context of a definition phrase or sentence. The ancestor <p> element, the
* <dt>/<dd> pairing, or the nearest <section> ancestor of the <dfn> element, is
* considered to be the definition of the term.
*
* The term being defined is identified following these rules:
*
* * If the <dfn> element has a title attribute, the value of the title
* attribute is considered to be the term being defined. The element must
* still have text within it, but that text may be an abbreviation (perhaps
* using <abbr>) or another form of the term.
* * If the <dfn> contains a single child element and does not have any text
* content of its own, and the child element is an <abbr> element with a
* title attribute itself, then the exact value of the <abbr> element's title
* is the term being defined.
* * Otherwise, the text content of the <dfn> element is the term being
* defined. This is shown in the first example below.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dfn
*/
class DataTag extends AbstractContainerTag
{
const TAG = 'dfn';
/**
* If the <dfn> element has a title attribute, the value of the title
* attribute is considered to be the term being defined. The element must
* still have text within it, but that text may be an abbreviation (perhaps
* using <abbr>) or another form of the term.
*
* @return null|string|Stringable
*/
public function title(): null|string|Stringable
{
return $this->attributes()->asString('title');
}
/**
* If the <dfn> element has a title attribute, the value of the title
* attribute is considered to be the term being defined. The element must
* still have text within it, but that text may be an abbreviation (perhaps
* using <abbr>) or another form of the term.
*
* @param null|string|Stringable $title
* @return static
*/
public function setTitle(null|string|Stringable $title): static
{
if ($title) $this->attributes()['title'] = $title;
else $this->unsetTitle();
return $this;
}
/**
* If the <dfn> element has a title attribute, the value of the title
* attribute is considered to be the term being defined. The element must
* still have text within it, but that text may be an abbreviation (perhaps
* using <abbr>) or another form of the term.
*
* @return static
*/
public function unsetTitle(): static
{
unset($this->attributes()['title']);
return $this;
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <em> HTML element marks text that has stress emphasis. The <em> element
* can be nested, with each level of nesting indicating a greater degree of
* emphasis.
*
* The <em> element is for words that have a stressed emphasis compared to
* surrounding text, which is often limited to a word or words of a sentence and
* affects the meaning of the sentence itself.
*
* Typically this element is displayed in italic type. However, it should not be
* used to apply italic styling; use the CSS font-style property for that
* purpose. Use the <cite> element to mark the title of a work (book, play,
* song, etc.). Use the <i> element to mark text that is in an alternate tone or
* mood, which covers many common situations for italics such as scientific
* names or words in other languages. Use the <strong> element to mark text that
* has greater importance than surrounding text.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em
*/
class EmTag extends AbstractContainerTag
{
const TAG = 'em';
}

View file

@ -0,0 +1,20 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <i> HTML element represents a range of text that is set off from the
* normal text for some reason, such as idiomatic text, technical terms,
* taxonomical designations, among others. Historically, these have been
* presented using italicized type, which is the original source of the <i>
* naming of this element.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i
*/
class ITag extends AbstractContainerTag
{
const TAG = 'i';
}

View file

@ -0,0 +1,27 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <kbd> HTML element represents a span of inline text denoting textual user
* input from a keyboard, voice input, or any other text entry device. By
* convention, the user agent defaults to rendering the contents of a <kbd>
* element using its default monospace font, although this is not mandated by
* the HTML standard.
*
* To describe an input comprised of multiple keystrokes, you can nest multiple
* <kbd> elements, with an outer <kbd> element representing the overall input
* and each individual keystroke or component of the input enclosed within its
* own <kbd>, such as:
*
* <kbd><kbd>Ctrl</kbd>+<kbd>N</kbd></kbd>
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd
*/
class KbdTag extends AbstractContainerTag
{
const TAG = 'kbd';
}

View file

@ -0,0 +1,18 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <mark> HTML element represents text which is marked or highlighted for
* reference or notation purposes due to the marked passage's relevance in the
* enclosing context.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark
*/
class MarkTag extends AbstractContainerTag
{
const TAG = 'mark';
}

View file

@ -0,0 +1,60 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
use Stringable;
/**
* The <q> HTML element indicates that the enclosed text is a short inline
* quotation. Most modern browsers implement this by surrounding the text in
* quotation marks. This element is intended for short quotations that don't
* require paragraph breaks; for long quotations use the <blockquote> element.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q
*/
class QTag extends AbstractContainerTag
{
const TAG = 'q';
/**
* The value of this attribute is a URL that designates a source document or
* message for the information quoted. This attribute is intended to point
* to information explaining the context or the reference for the quote.
*
* @return null|string|Stringable
*/
public function cite(): null|string|Stringable
{
return $this->attributes()->asString('cite');
}
/**
* The value of this attribute is a URL that designates a source document or
* message for the information quoted. This attribute is intended to point
* to information explaining the context or the reference for the quote.
*
* @param null|string|Stringable $cite
* @return static
*/
public function setCite(null|string|Stringable $cite): static
{
if ($cite) $this->attributes()['cite'] = $cite;
else $this->unsetCite();
return $this;
}
/**
* The value of this attribute is a URL that designates a source document or
* message for the information quoted. This attribute is intended to point
* to information explaining the context or the reference for the quote.
*
* @return static
*/
public function unsetCite(): static
{
unset($this->attributes()['cite']);
return $this;
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <rp> HTML element is used to provide fall-back parentheses for browsers
* that do not support display of ruby annotations using the <ruby> element. One
* <rp> element should enclose each of the opening and closing parentheses that
* wrap the <rt> element that contains the annotation's text.
*
* Ruby annotations are for showing pronunciation of East Asian characters, like
* using Japanese furigana or Taiwanese bopomofo characters. The <rp> element is
* used in the case of lack of <ruby> element support; the <rp> content provides
* what should be displayed in order to indicate the presence of a ruby
* annotation, usually parentheses.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rp
*/
class RpTag extends AbstractContainerTag
{
const TAG = 'rp';
}

View file

@ -0,0 +1,24 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <rt> HTML element specifies the ruby text component of a ruby annotation,
* which is used to provide pronunciation, translation, or transliteration
* information for East Asian typography. The <rt> element must always be
* contained within a <ruby> element.
*
* This simple example provides Romaji transliteration for the kanji characters
* within the <ruby> element:
*
* <ruby> <rt>Kan</rt> <rt>ji</rt> </ruby>
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rt
*/
class RtTag extends AbstractContainerTag
{
const TAG = 'rt';
}

View file

@ -0,0 +1,32 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <ruby> HTML element represents small annotations that are rendered above,
* below, or next to base text, usually used for showing the pronunciation of
* East Asian characters. It can also be used for annotating other kinds of
* text, but this usage is less common.
*
* Example 1: Character:
* ```
* <ruby>
* <rp>(</rp><rt>Kan</rt><rp>)</rp>
* <rp>(</rp><rt>ji</rt><rp>)</rp>
* </ruby>
* ```
*
* Example 2: Word
* ```
* <ruby> 明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp> </ruby>
* ```
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby
*/
class RubyTag extends AbstractContainerTag
{
const TAG = 'ruby';
}

View file

@ -0,0 +1,24 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <s> HTML element renders text with a strikethrough, or a line through it.
* Use the <s> element to represent things that are no longer relevant or no
* longer accurate. However, <s> is not appropriate when indicating document
* edits; for that, use the <del> and <ins> elements, as appropriate.
*
* Accessibility concerns: The presence of the s element is not announced by
* most screen reading technology in its default configuration. It can be made
* to be announced by using the CSS content property, along with the ::before
* and ::after pseudo-elements.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s
*/
class STag extends AbstractContainerTag
{
const TAG = 's';
}

View file

@ -0,0 +1,19 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <samp> HTML element is used to enclose inline text which represents
* sample (or quoted) output from a computer program. Its contents are typically
* rendered using the browser's default monospaced font (such as Courier or
* Lucida Console).
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/samp
*/
class SampTag extends AbstractContainerTag
{
const TAG = 'samp';
}

View file

@ -0,0 +1,19 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <small> HTML element represents side-comments and small print, like
* copyright and legal text, independent of its styled presentation. By default,
* it renders text within it one font-size smaller, such as from small to
* x-small.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small
*/
class SmallTag extends AbstractContainerTag
{
const TAG = 'small';
}

View file

@ -0,0 +1,22 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <span> HTML element is a generic inline container for phrasing content,
* which does not inherently represent anything. It can be used to group
* elements for styling purposes (using the class or id attributes), or because
* they share attribute values, such as lang. It should be used only when no
* other semantic element is appropriate. <span> is very much like a <div>
* element, but <div> is a block-level element whereas a <span> is an
* inline-level element.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span
*/
class SpanTag extends AbstractContainerTag
{
const TAG = 'span';
}

View file

@ -0,0 +1,23 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <strong> HTML element indicates that its contents have strong importance,
* seriousness, or urgency. Browsers typically render the contents in bold type.
*
* The <strong> element is for content that is of "strong importance," including
* things of great seriousness or urgency (such as warnings). This could be a
* sentence that is of great importance to the whole page, or you could merely
* try to point out that some words are of greater importance compared to nearby
* content.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong
*/
class StrongTag extends AbstractContainerTag
{
const TAG = 'strong';
}

View file

@ -0,0 +1,18 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <sub> HTML element specifies inline text which should be displayed as
* subscript for solely typographical reasons. Subscripts are typically rendered
* with a lowered baseline using smaller text.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub
*/
class SubTag extends AbstractContainerTag
{
const TAG = 'sub';
}

View file

@ -0,0 +1,18 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
* The <sup> HTML element specifies inline text which is to be displayed as
* superscript for solely typographical reasons. Superscripts are usually
* rendered with a raised baseline using smaller text.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup
*/
class SupTag extends AbstractContainerTag
{
const TAG = 'sup';
}

View file

@ -0,0 +1,49 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics;
use ByJoby\HTML\Html5\InlineTextSemantics\TimeTag\DatetimeValue;
use ByJoby\HTML\Tags\AbstractContainerTag;
use DateInterval;
use DateTimeInterface;
use Stringable;
/**
* The <time> HTML element represents a specific period in time. It may include
* the datetime attribute to translate dates into machine-readable format,
* allowing for better search engine results or custom features such as
* reminders.
*
* It may represent one of the following:
* * A time on a 24-hour clock.
* * A precise date in the Gregorian calendar (with optional time and timezone
* information).
* * A valid time duration.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time
*/
class TimeTag extends AbstractContainerTag
{
const TAG = 'time';
public function datetime(): null|DatetimeValue
{
return DatetimeValue::fromString(
$this->attributes()->asString('datetime')
);
}
public function setDatetime(null|DatetimeValue $datetime): static
{
if ($datetime) $this->attributes()['datetime'] = $datetime;
else $this->unsetDatetime();
return $this;
}
public function unsetDatetime(): static
{
unset($this->attributes()['datetime']);
return $this;
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use ByJoby\HTML\Helpers\StringableValue;
use Stringable;
abstract class DatetimeValue implements StringableValue
{
/** @var array<int,class-string<DatetimeValue>> */
const SUBCLASSES = [
DatetimeValue_datetime::class,
DatetimeValue_datetime_local::class,
DatetimeValue_time::class,
DatetimeValue_year::class,
DatetimeValue_month::class,
DatetimeValue_date::class,
DatetimeValue_date_yearless::class,
DatetimeValue_week::class,
DatetimeValue_duration::class,
];
/**
* Matches any sequence of at least four digits, optionally prefixed with a
* dash for negative (BCE) years
*/
const REGEX_YEAR = '(?<year>(\-)?[0-9]{4,})';
/**
* Matches any integer from 01 to 12, leading zero optional
*/
const REGEX_MONTH = '(?<month>0?[1-9]|1[0-2])';
/**
* Matches any integer from 01 to 31, leading zero optional
*/
const REGEX_DAY = '(?<day>0?[1-9]|[0-2][0-9]|3[0-1])';
/**
* Matches any integer from 01 to 53, leading zero optional
*/
const REGEX_WEEKNUM = '(?<weeknum>0?[1-9]|[1-4][0-9]|5[0-3])';
/**
* Matches any integer from 00 to 59, leading zero optional
*/
const REGEX_HOUR = '(?<hour>0?[0-9]|1[0-9]|2[0-3])';
/**
* Matches any integer from 00 to 59, leading zero optional
*/
const REGEX_MINUTE = '(?<minute>0?[0-9]|[1-5][0-9])';
/**
* Matches any integer from 00 to 59, leading zero optional. Optionally
* followed by a three-digit decimal portion.
*/
const REGEX_SECOND = '((?<second>0?[0-9]|[1-5][0-9])(\.(?<microsecond>[0-9]{3}))?)';
/**
* Tries parsing with all subclasses and returns the first one that
* succeeds, or null if nothing does.
*
* @param string|Stringable|null $string
* @return null|self
*/
public static function fromString(string|Stringable|null $string): null|self
{
if (is_null($string)) return null;
foreach (static::SUBCLASSES as $class) {
$result = $class::fromString(strval($string));
if ($result) return $result;
}
return null;
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use DateTime;
use Stringable;
/**
* Holds a date that will be stringed to something like 2011-11-18
*
* Stored internally as a DateTime with the time set to noon, which is public
* and as such can be conveniently manipulated.
*/
class DatetimeValue_date extends DatetimeValue
{
/** @var DateTime */
public $datetime;
public static function fromString(string|Stringable|null $string): null|self
{
// null string returns null
if (is_null($string)) return null;
// try to match regular expression
elseif (
preg_match(
sprintf(
'/^%s\-%s\-%s$/',
static::REGEX_YEAR,
static::REGEX_MONTH,
static::REGEX_DAY,
),
$string,
$matches
)
) {
return new self(
intval($matches['year']),
intval($matches['month']),
intval($matches['day']),
);
}
// return null if nothing found
return null;
}
public function __construct(int $year, int $month, int $day)
{
$this->datetime = (new DateTime())
->setDate($year,$month,$day)
->setTime(12,0,0,0);
}
public function __toString()
{
return $this->datetime->format('Y-m-d');
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use DateTime;
use Stringable;
/**
* Holds a date that will be stringed to something like 11-18
*
* Stored internally as a DateTime with the time set to noon, which is public
* and as such can be conveniently manipulated. Internally the year will be set
* to the current year.
*/
class DatetimeValue_date_yearless extends DatetimeValue
{
/** @var DateTime */
public $datetime;
public static function fromString(string|Stringable|null $string): null|self
{
// null string returns null
if (is_null($string)) return null;
// try to match regular expression
elseif (
preg_match(
sprintf(
'/^%s\-%s$/',
static::REGEX_MONTH,
static::REGEX_DAY,
),
$string,
$matches
)
) {
return new self(
intval($matches['month']),
intval($matches['day']),
);
}
// return null if nothing found
return null;
}
public function __construct(int $month, int $day)
{
$this->datetime = (new DateTime())
->setDate(intval(date('Y')), $month, $day)
->setTime(12, 0, 0, 0);
}
public function __toString()
{
return $this->datetime->format('m-d');
}
}

View file

@ -0,0 +1,84 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use DateTime;
use DateTimeZone;
use Stringable;
/**
* Holds a date/time that will be stringed to something like
* 2011-11-18T14:54:39.929-0600
*
* Stored internally as a DateTime, which is public and as such can be
* conveniently manipulated.
*/
class DatetimeValue_datetime extends DatetimeValue
{
/**
* Matches either "Z" or a positive or negative offset from GMT in which the
* colon is optional, such as +04:00 or -1030
*/
const REGEX_TIMEZONE = '(?<timezone>Z|(\+|\-)(0[0-9]|1[0-9]|2[0-3]):?(0[0-9]|[0-5][0-9]))';
public static function fromString(string|Stringable|null $string): null|self
{
// null string returns null
if (is_null($string)) return null;
// try to match regular expression
elseif (
preg_match(
sprintf(
'/^%s\-%s\-%s(T| )%s:%s(:%s)?%s$/i',
static::REGEX_YEAR,
static::REGEX_MONTH,
static::REGEX_DAY,
static::REGEX_HOUR,
static::REGEX_MINUTE,
static::REGEX_SECOND,
static::REGEX_TIMEZONE,
),
$string,
$matches
)
) {
return new self(
(new DateTime())
->setTimezone(
self::parseTimezone($matches['timezone'])
)
->setDate(
intval($matches['year']),
intval($matches['month']),
intval($matches['day'])
)
->setTime(
intval($matches['hour']),
intval($matches['minute']),
intval(@$matches['second']),
intval(@$matches['millisecond']) * 1000
)
);
}
// return null if nothing found
return null;
}
protected static function parseTimezone(string $timezone): DateTimeZone
{
if ($timezone == 'Z' || $timezone == 'z') {
return new DateTimeZone('UTC');
} else {
return new DateTimeZone(str_replace(':', '', $timezone));
}
}
public function __construct(public DateTime $datetime)
{
}
public function __toString()
{
return $this->datetime->format('c');
}
}

View file

@ -0,0 +1,65 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use DateTime;
use Stringable;
/**
* Holds a date/time that will be stringed to something like
* 2011-11-18T14:54:39.929 for use as a local time without an attached timezone.
*
* Stored internally as a DateTime, which is public and as such can be
* conveniently manipulated.
*/
class DatetimeValue_datetime_local extends DatetimeValue
{
/** @var DateTime */
public $datetime;
public static function fromString(string|Stringable|null $string): null|self
{
// null string returns null
if (is_null($string)) return null;
// try to match regular expression
elseif (
preg_match(
sprintf(
'/^%s\-%s\-%s(t|T| )%s:%s(:%s)?$/',
static::REGEX_YEAR,
static::REGEX_MONTH,
static::REGEX_DAY,
static::REGEX_HOUR,
static::REGEX_MINUTE,
static::REGEX_SECOND,
),
$string,
$matches
)
) {
return new self(
intval($matches['year']),
intval($matches['month']),
intval($matches['day']),
intval($matches['hour']),
intval($matches['minute']),
intval(@$matches['second']),
intval(@$matches['millisecond'])
);
}
// return null if nothing found
return null;
}
public function __construct(int $year, int $month, int $day, int $hour, int $minute, int $second = 0, int $millisecond = 0)
{
$this->datetime = (new DateTime())
->setDate($year, $month, $day)
->setTime($hour, $minute, $second, $millisecond * 1000);
}
public function __toString()
{
return $this->datetime->format('Y-m-d\TH:i:s.v');
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use DateInterval;
use Stringable;
/**
* Holds an interval/duration that will be stringed to something like PT4H18M3S
*/
class DatetimeValue_duration extends DatetimeValue
{
/**
* Matches a valid duration period designation
*/
const REGEX_DURATION = "(<duration>P([0-9]+Y)?([0-9]+M)?([0-9]+[WD])?(T([0-9]+H)?([0-9]+M)?([0-9]+S)?)?)";
public static function fromString(string|Stringable|null $string): null|self
{
// null string returns null
if (is_null($string)) return null;
// try to construct
try {
return new self(
new DateInterval(strval($string))
);
} catch (\Throwable $th) {
return null;
}
}
public function __construct(protected DateInterval $interval)
{
}
public function __toString()
{
// format with all fields
$string = $this->interval->format('P%yY%mM%dDT%hH%iM%sS');
// strip out fields that are zero
/** @var string */
$string = preg_replace('/0[YMDHMS]/', '', $string);
// strip trailing T if necessary
if (str_ends_with($string, 'T')) $string = substr($string, 0, strlen($string) - 1);
// return cleaned up value
return $string;
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use Stringable;
/**
* Holds a year/month pair that will be stringed to something like 2011-11
*/
class DatetimeValue_month extends DatetimeValue
{
public static function fromString(string|Stringable|null $string): null|self
{
// null string returns null
if (is_null($string)) return null;
// try to match regular expression
elseif (
preg_match(
sprintf(
'/^%s\-%s$/',
static::REGEX_YEAR,
static::REGEX_MONTH
),
$string,
$matches
)
) {
return new self(
intval($matches['year']),
intval($matches['month'])
);
}
// return null if nothing found
return null;
}
public function __construct(public int $year, public int $week)
{
}
public function __toString()
{
return sprintf(
'%s%04d-%02d',
$this->year < 0 ? '-' : '',
abs($this->year),
$this->week
);
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use DateTime;
use Stringable;
/**
* Holds a time that will be stringed to something like 14.54:39.929
*
* Stored internally as a DateTime with the date set to today, which is public
* and as such can be conveniently manipulated.
*/
class DatetimeValue_time extends DatetimeValue
{
/** @var DateTime */
public $datetime;
public static function fromString(string|Stringable|null $string): null|self
{
// null string returns null
if (is_null($string)) return null;
// try to match regular expression
elseif (
preg_match(
sprintf(
'/^%s:%s(:%s)?$/',
static::REGEX_HOUR,
static::REGEX_MINUTE,
static::REGEX_SECOND,
),
$string,
$matches
)
) {
return new self(
intval($matches['hour']),
intval($matches['minute']),
intval(@$matches['second']),
intval(@$matches['microsecond'])
);
}
// return null if nothing found
return null;
}
public function __construct(int $hour, int $minute, int $second = 0, int $millisecond = 0)
{
$this->datetime = (new DateTime())
->setTime($hour, $minute, $second, $millisecond*1000);
}
public function __toString()
{
return $this->datetime->format('H:i:s.v');
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use Stringable;
/**
* Holds a year/week pair that will be stringed to something like 2011-W47
*/
class DatetimeValue_week extends DatetimeValue
{
public static function fromString(string|Stringable|null $string): null|self
{
// null string returns null
if (is_null($string)) return null;
// try to match regular expression
elseif (
preg_match(
sprintf(
'/^%s\-W%s$/i',
static::REGEX_YEAR,
static::REGEX_WEEKNUM
),
$string,
$matches
)
) {
return new self(
intval($matches['year']),
intval($matches['weeknum'])
);
}
// return null if nothing found
return null;
}
public function __construct(public int $year, public int $week)
{
}
public function __toString()
{
return sprintf(
'%s%04d-%02d',
$this->year < 0 ? '-' : '',
abs($this->year),
$this->week
);
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use Stringable;
class DatetimeValue_year extends DatetimeValue
{
public static function fromString(string|Stringable|null $string): null|self
{
// null string returns null
if (is_null($string)) return null;
// try to match regular expression
elseif (
preg_match(
sprintf(
'/^%s$/',
static::REGEX_YEAR
),
$string,
$matches
)
) {
return new self(
intval($matches['year'])
);
}
// return null if nothing found
return null;
}
public function __construct(protected int $year)
{
}
public function __toString()
{
return sprintf(
'%s%04d',
$this->year < 0 ? '-' : '',
abs($this->year)
);
}
}

View file

@ -0,0 +1,64 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use PHPUnit\Framework\TestCase;
class DatetimeValue_date_Test extends TestCase
{
public function testFromValidStrings(): void
{
// example from mdn
$this->assertEquals(
"2011-11-18",
DatetimeValue_date::fromString("2011-11-18")
?->__toString()
);
// bottom of range (test zero year)
$this->assertEquals(
"0000-01-01",
DatetimeValue_date::fromString("0000-1-1")
?->__toString()
);
// top of range
$this->assertEquals(
"99999-12-31",
DatetimeValue_date::fromString("99999-12-31")
?->__toString()
);
// negative years (BCE)
$this->assertEquals(
"-1000-12-31",
DatetimeValue_date::fromString("-1000-12-31")
?->__toString()
);
}
public function testFromInvalidStrings(): void
{
// too short year
$this->assertNull(
DatetimeValue_date::fromString("999-1-1")
);
// below range
$this->assertNull(
DatetimeValue_date::fromString("1999-0-1")
);
$this->assertNull(
DatetimeValue_date::fromString("1999-1-0")
);
$this->assertNull(
DatetimeValue_date::fromString("1999-00-00")
);
// above range
$this->assertNull(
DatetimeValue_date::fromString("1999-13-02")
);
$this->assertNull(
DatetimeValue_date::fromString("1999-02-32")
);
$this->assertNull(
DatetimeValue_date::fromString("1999-13-32")
);
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use PHPUnit\Framework\TestCase;
class DatetimeValue_month_Test extends TestCase
{
public function testFromValidStrings(): void
{
// example from mdn
$this->assertEquals(
"2011-11",
DatetimeValue_month::fromString("2011-11")
?->__toString()
);
// bottom of range
$this->assertEquals(
"0000-01",
DatetimeValue_month::fromString("0000-01")
?->__toString()
);
// top of range
$this->assertEquals(
"99999-12",
DatetimeValue_month::fromString("99999-12")
?->__toString()
);
// negative year
$this->assertEquals(
"-1500-06",
DatetimeValue_month::fromString("-1500-06")
?->__toString()
);
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use PHPUnit\Framework\TestCase;
class DatetimeValue_time_Test extends TestCase
{
public function testFromValidStrings(): void
{
// examples from mdn
$this->assertEquals(
"14:54:00.000",
DatetimeValue_time::fromString("14:54")
?->__toString()
);
$this->assertEquals(
"14:54:39.000",
DatetimeValue_time::fromString("14:54:39")
?->__toString()
);
$this->assertEquals(
"14:54:39.929",
DatetimeValue_time::fromString("14:54:39.929")
?->__toString()
);
// bottom of range
$this->assertEquals(
"00:00:00.000",
DatetimeValue_time::fromString("00:00:00.000")
?->__toString()
);
// top of range
$this->assertEquals(
"23:59:59.999",
DatetimeValue_time::fromString("23:59:59.999")
?->__toString()
);
}
public function testFromInvalidStrings(): void
{
// hour too big
$this->assertNull(
DatetimeValue_time::fromString("24:00:00.000")
);
// minute too big
$this->assertNull(
DatetimeValue_time::fromString("00:60:00.000")
);
// second too big
$this->assertNull(
DatetimeValue_time::fromString("00:00:60.000")
);
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use PHPUnit\Framework\TestCase;
class DatetimeValue_week_Test extends TestCase
{
public function testFromValidStrings(): void
{
// example from mdn
$this->assertEquals(
"2011-W47",
DatetimeValue_week::fromString("2011-W47")
?->__toString()
);
// bottom of range
$this->assertEquals(
"0000-W01",
DatetimeValue_week::fromString("0000-W01")
?->__toString()
);
// top of range
$this->assertEquals(
"99999-W53",
DatetimeValue_week::fromString("99999-W53")
?->__toString()
);
// negative year
$this->assertEquals(
"-1500-06",
DatetimeValue_week::fromString("-1500-06")
?->__toString()
);
}
public function testFromInvalidStrings(): void
{
// week too small
$this->assertNull(
DatetimeValue_week::fromString("0000-W00")
);
// week too big
$this->assertNull(
DatetimeValue_week::fromString("0000-W54")
);
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use PHPUnit\Framework\TestCase;
class DatetimeValue_year_Test extends TestCase
{
public function testFromValidStrings(): void
{
// example from mdn
$this->assertEquals(
"2011",
DatetimeValue_year::fromString("2011")
?->__toString()
);
// bottom of range
$this->assertEquals(
"0000",
DatetimeValue_year::fromString("0000")
?->__toString()
);
// top of range
$this->assertEquals(
"99999",
DatetimeValue_year::fromString("99999")
?->__toString()
);
// negative year with only three digits
$this->assertEquals(
"-0150",
DatetimeValue_year::fromString("-0150")
?->__toString()
);
}
public function testFromInvalidStrings(): void
{
// year is too short
$this->assertNull(
DatetimeValue_year::fromString("123")
);
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use PHPUnit\Framework\TestCase;
class DatetimeValue_date_yearless_Test extends TestCase
{
public function testFromValidStrings(): void
{
// example from mdn
$this->assertEquals(
"11-18",
DatetimeValue_date_yearless::fromString("11-18")
?->__toString()
);
// bottom of range
$this->assertEquals(
"01-01",
DatetimeValue_date_yearless::fromString("1-1")
?->__toString()
);
// top of range
$this->assertEquals(
"12-31",
DatetimeValue_date_yearless::fromString("12-31")
?->__toString()
);
}
public function testFromInvalidStrings(): void
{
// below range
$this->assertNull(
DatetimeValue_date_yearless::fromString("0-1")
);
$this->assertNull(
DatetimeValue_date_yearless::fromString("1-0")
);
$this->assertNull(
DatetimeValue_date_yearless::fromString("00-00")
);
// above range
$this->assertNull(
DatetimeValue_date_yearless::fromString("13-02")
);
$this->assertNull(
DatetimeValue_date_yearless::fromString("02-32")
);
$this->assertNull(
DatetimeValue_date_yearless::fromString("13-32")
);
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use PHPUnit\Framework\TestCase;
class DatetimeValue_datetime_Test extends TestCase
{
public function testFromValidStrings(): void
{
// example from mdn
$this->assertEquals(
"2011-11-18T14:54:39+00:00",
DatetimeValue_datetime::fromString("2011-11-18T14:54:39Z")
?->__toString()
);
// bottom of range
$this->assertEquals(
"0000-01-01T00:00:00-23:59",
DatetimeValue_datetime::fromString("0000-01-01 00:00:00-23:59")
?->__toString()
);
// top of range
$this->assertEquals(
"99999-12-31T23:59:59+23:59",
DatetimeValue_datetime::fromString("99999-12-31T23:59:59+23:59")
?->__toString()
);
// negative years (BCE)
$this->assertEquals(
"-1000-12-31T23:59:59+00:00",
DatetimeValue_datetime::fromString("-1000-12-31T23:59:59+0000")
?->__toString()
);
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use PHPUnit\Framework\TestCase;
class DatetimeValue_datetime_local_Test extends TestCase
{
public function testFromValidStrings(): void
{
// example from mdn
$this->assertEquals(
"2011-11-18T14:54:39.929",
DatetimeValue_datetime_local::fromString("2011-11-18T14:54:39.929")
?->__toString()
);
// bottom of range (test zero year)
$this->assertEquals(
"0000-01-01T00:00:00.000",
DatetimeValue_datetime_local::fromString("0000-01-01 00:00:00.000")
?->__toString()
);
// top of range
$this->assertEquals(
"99999-12-31T23:59:59.999",
DatetimeValue_datetime_local::fromString("99999-12-31T23:59:59.999")
?->__toString()
);
// negative years (BCE)
$this->assertEquals(
"-1000-12-31T23:59:59.999",
DatetimeValue_datetime_local::fromString("-1000-12-31T23:59:59.999")
?->__toString()
);
}
public function testFromInvalidStrings(): void
{
// too short microseconds
$this->assertNull(
DatetimeValue_datetime_local::fromString("1999-1-1T23:59:59.99")
);
// too short year
$this->assertNull(
DatetimeValue_datetime_local::fromString("999-1-1T23:59:59.999")
);
// below range
$this->assertNull(
DatetimeValue_datetime_local::fromString("1999-0-1T23:59:59.999")
);
$this->assertNull(
DatetimeValue_datetime_local::fromString("1999-1-0T23:59:59.999")
);
$this->assertNull(
DatetimeValue_datetime_local::fromString("1999-00-00T23:59:59.999")
);
// above range
$this->assertNull(
DatetimeValue_datetime_local::fromString("1999-13-02T23:59:59.999")
);
$this->assertNull(
DatetimeValue_datetime_local::fromString("1999-02-32T23:59:59.999")
);
$this->assertNull(
DatetimeValue_datetime_local::fromString("1999-13-32T23:59:59.999")
);
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace ByJoby\HTML\Html5\InlineTextSemantics\TimeTag;
use PHPUnit\Framework\TestCase;
/**
* Honestly this one doesn't need a lot of testing, because it just uses PHP's
* DateInterval built-in parsing
*/
class DatetimeValue_duration_Test extends TestCase
{
public function testFromValidStrings(): void
{
// example from mdn
$this->assertEquals(
"PT4H18M3S",
DatetimeValue_duration::fromString("PT4H18M3S")
?->__toString()
);
// bottom of range
$this->assertEquals(
"PT1S",
DatetimeValue_duration::fromString("PT1S")
?->__toString()
);
// all values
$this->assertEquals(
"P99Y99M99DT99H99M99S",
DatetimeValue_duration::fromString("P99Y99M99DT99H99M99S")
?->__toString()
);
}
}