Initial commit
This commit is contained in:
61
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/CharacterReference.php
vendored
Normal file
61
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/CharacterReference.php
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Masterminds\HTML5\Parser;
|
||||
|
||||
use Masterminds\HTML5\Entities;
|
||||
|
||||
/**
|
||||
* Manage entity references.
|
||||
*
|
||||
* This is a simple resolver for HTML5 character reference entitites. See Entities for the list of supported entities.
|
||||
*/
|
||||
class CharacterReference
|
||||
{
|
||||
protected static $numeric_mask = array(
|
||||
0x0,
|
||||
0x2FFFF,
|
||||
0,
|
||||
0xFFFF,
|
||||
);
|
||||
|
||||
/**
|
||||
* Given a name (e.g. 'amp'), lookup the UTF-8 character ('&').
|
||||
*
|
||||
* @param string $name The name to look up.
|
||||
*
|
||||
* @return string The character sequence. In UTF-8 this may be more than one byte.
|
||||
*/
|
||||
public static function lookupName($name)
|
||||
{
|
||||
// Do we really want to return NULL here? or FFFD
|
||||
return isset(Entities::$byName[$name]) ? Entities::$byName[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a decimal number, return the UTF-8 character.
|
||||
*
|
||||
* @param $int
|
||||
*
|
||||
* @return false|string|string[]|null
|
||||
*/
|
||||
public static function lookupDecimal($int)
|
||||
{
|
||||
$entity = '&#' . $int . ';';
|
||||
|
||||
// UNTESTED: This may fail on some planes. Couldn't find full documentation
|
||||
// on the value of the mask array.
|
||||
return mb_decode_numericentity($entity, static::$numeric_mask, 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a hexidecimal number, return the UTF-8 character.
|
||||
*
|
||||
* @param $hexdec
|
||||
*
|
||||
* @return false|string|string[]|null
|
||||
*/
|
||||
public static function lookupHex($hexdec)
|
||||
{
|
||||
return static::lookupDecimal(hexdec($hexdec));
|
||||
}
|
||||
}
|
||||
705
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/DOMTreeBuilder.php
vendored
Normal file
705
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/DOMTreeBuilder.php
vendored
Normal file
@@ -0,0 +1,705 @@
|
||||
<?php
|
||||
|
||||
namespace Masterminds\HTML5\Parser;
|
||||
|
||||
use Masterminds\HTML5\Elements;
|
||||
use Masterminds\HTML5\InstructionProcessor;
|
||||
|
||||
/**
|
||||
* Create an HTML5 DOM tree from events.
|
||||
*
|
||||
* This attempts to create a DOM from events emitted by a parser. This
|
||||
* attempts (but does not guarantee) to up-convert older HTML documents
|
||||
* to HTML5. It does this by applying HTML5's rules, but it will not
|
||||
* change the architecture of the document itself.
|
||||
*
|
||||
* Many of the error correction and quirks features suggested in the specification
|
||||
* are implemented herein; however, not all of them are. Since we do not
|
||||
* assume a graphical user agent, no presentation-specific logic is conducted
|
||||
* during tree building.
|
||||
*
|
||||
* FIXME: The present tree builder does not exactly follow the state machine rules
|
||||
* for insert modes as outlined in the HTML5 spec. The processor needs to be
|
||||
* re-written to accomodate this. See, for example, the Go language HTML5
|
||||
* parser.
|
||||
*/
|
||||
class DOMTreeBuilder implements EventHandler
|
||||
{
|
||||
/**
|
||||
* Defined in http://www.w3.org/TR/html51/infrastructure.html#html-namespace-0.
|
||||
*/
|
||||
const NAMESPACE_HTML = 'http://www.w3.org/1999/xhtml';
|
||||
|
||||
const NAMESPACE_MATHML = 'http://www.w3.org/1998/Math/MathML';
|
||||
|
||||
const NAMESPACE_SVG = 'http://www.w3.org/2000/svg';
|
||||
|
||||
const NAMESPACE_XLINK = 'http://www.w3.org/1999/xlink';
|
||||
|
||||
const NAMESPACE_XML = 'http://www.w3.org/XML/1998/namespace';
|
||||
|
||||
const NAMESPACE_XMLNS = 'http://www.w3.org/2000/xmlns/';
|
||||
|
||||
const OPT_DISABLE_HTML_NS = 'disable_html_ns';
|
||||
|
||||
const OPT_TARGET_DOC = 'target_document';
|
||||
|
||||
const OPT_IMPLICIT_NS = 'implicit_namespaces';
|
||||
|
||||
/**
|
||||
* Holds the HTML5 element names that causes a namespace switch.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $nsRoots = array(
|
||||
'html' => self::NAMESPACE_HTML,
|
||||
'svg' => self::NAMESPACE_SVG,
|
||||
'math' => self::NAMESPACE_MATHML,
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds the always available namespaces (which does not require the XMLNS declaration).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $implicitNamespaces = array(
|
||||
'xml' => self::NAMESPACE_XML,
|
||||
'xmlns' => self::NAMESPACE_XMLNS,
|
||||
'xlink' => self::NAMESPACE_XLINK,
|
||||
);
|
||||
|
||||
/**
|
||||
* Holds a stack of currently active namespaces.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $nsStack = array();
|
||||
|
||||
/**
|
||||
* Holds the number of namespaces declared by a node.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $pushes = array();
|
||||
|
||||
/**
|
||||
* Defined in 8.2.5.
|
||||
*/
|
||||
const IM_INITIAL = 0;
|
||||
|
||||
const IM_BEFORE_HTML = 1;
|
||||
|
||||
const IM_BEFORE_HEAD = 2;
|
||||
|
||||
const IM_IN_HEAD = 3;
|
||||
|
||||
const IM_IN_HEAD_NOSCRIPT = 4;
|
||||
|
||||
const IM_AFTER_HEAD = 5;
|
||||
|
||||
const IM_IN_BODY = 6;
|
||||
|
||||
const IM_TEXT = 7;
|
||||
|
||||
const IM_IN_TABLE = 8;
|
||||
|
||||
const IM_IN_TABLE_TEXT = 9;
|
||||
|
||||
const IM_IN_CAPTION = 10;
|
||||
|
||||
const IM_IN_COLUMN_GROUP = 11;
|
||||
|
||||
const IM_IN_TABLE_BODY = 12;
|
||||
|
||||
const IM_IN_ROW = 13;
|
||||
|
||||
const IM_IN_CELL = 14;
|
||||
|
||||
const IM_IN_SELECT = 15;
|
||||
|
||||
const IM_IN_SELECT_IN_TABLE = 16;
|
||||
|
||||
const IM_AFTER_BODY = 17;
|
||||
|
||||
const IM_IN_FRAMESET = 18;
|
||||
|
||||
const IM_AFTER_FRAMESET = 19;
|
||||
|
||||
const IM_AFTER_AFTER_BODY = 20;
|
||||
|
||||
const IM_AFTER_AFTER_FRAMESET = 21;
|
||||
|
||||
const IM_IN_SVG = 22;
|
||||
|
||||
const IM_IN_MATHML = 23;
|
||||
|
||||
protected $options = array();
|
||||
|
||||
protected $stack = array();
|
||||
|
||||
protected $current; // Pointer in the tag hierarchy.
|
||||
protected $rules;
|
||||
protected $doc;
|
||||
|
||||
protected $frag;
|
||||
|
||||
protected $processor;
|
||||
|
||||
protected $insertMode = 0;
|
||||
|
||||
/**
|
||||
* Track if we are in an element that allows only inline child nodes.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $onlyInline;
|
||||
|
||||
/**
|
||||
* Quirks mode is enabled by default.
|
||||
* Any document that is missing the DT will be considered to be in quirks mode.
|
||||
*/
|
||||
protected $quirks = true;
|
||||
|
||||
protected $errors = array();
|
||||
|
||||
public function __construct($isFragment = false, array $options = array())
|
||||
{
|
||||
$this->options = $options;
|
||||
|
||||
if (isset($options[self::OPT_TARGET_DOC])) {
|
||||
$this->doc = $options[self::OPT_TARGET_DOC];
|
||||
} else {
|
||||
$impl = new \DOMImplementation();
|
||||
// XXX:
|
||||
// Create the doctype. For now, we are always creating HTML5
|
||||
// documents, and attempting to up-convert any older DTDs to HTML5.
|
||||
$dt = $impl->createDocumentType('html');
|
||||
// $this->doc = \DOMImplementation::createDocument(NULL, 'html', $dt);
|
||||
$this->doc = $impl->createDocument(null, '', $dt);
|
||||
$this->doc->encoding = !empty($options['encoding']) ? $options['encoding'] : 'UTF-8';
|
||||
}
|
||||
|
||||
$this->errors = array();
|
||||
|
||||
$this->current = $this->doc; // ->documentElement;
|
||||
|
||||
// Create a rules engine for tags.
|
||||
$this->rules = new TreeBuildingRules();
|
||||
|
||||
$implicitNS = array();
|
||||
if (isset($this->options[self::OPT_IMPLICIT_NS])) {
|
||||
$implicitNS = $this->options[self::OPT_IMPLICIT_NS];
|
||||
} elseif (isset($this->options['implicitNamespaces'])) {
|
||||
$implicitNS = $this->options['implicitNamespaces'];
|
||||
}
|
||||
|
||||
// Fill $nsStack with the defalut HTML5 namespaces, plus the "implicitNamespaces" array taken form $options
|
||||
array_unshift($this->nsStack, $implicitNS + array('' => self::NAMESPACE_HTML) + $this->implicitNamespaces);
|
||||
|
||||
if ($isFragment) {
|
||||
$this->insertMode = static::IM_IN_BODY;
|
||||
$this->frag = $this->doc->createDocumentFragment();
|
||||
$this->current = $this->frag;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the document.
|
||||
*/
|
||||
public function document()
|
||||
{
|
||||
return $this->doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DOM fragment for the body.
|
||||
*
|
||||
* This returns a DOMNodeList because a fragment may have zero or more
|
||||
* DOMNodes at its root.
|
||||
*
|
||||
* @see http://www.w3.org/TR/2012/CR-html5-20121217/syntax.html#concept-frag-parse-context
|
||||
*
|
||||
* @return \DOMDocumentFragment
|
||||
*/
|
||||
public function fragment()
|
||||
{
|
||||
return $this->frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an instruction processor.
|
||||
*
|
||||
* This is used for handling Processor Instructions as they are
|
||||
* inserted. If omitted, PI's are inserted directly into the DOM tree.
|
||||
*
|
||||
* @param InstructionProcessor $proc
|
||||
*/
|
||||
public function setInstructionProcessor(InstructionProcessor $proc)
|
||||
{
|
||||
$this->processor = $proc;
|
||||
}
|
||||
|
||||
public function doctype($name, $idType = 0, $id = null, $quirks = false)
|
||||
{
|
||||
// This is used solely for setting quirks mode. Currently we don't
|
||||
// try to preserve the inbound DT. We convert it to HTML5.
|
||||
$this->quirks = $quirks;
|
||||
|
||||
if ($this->insertMode > static::IM_INITIAL) {
|
||||
$this->parseError('Illegal placement of DOCTYPE tag. Ignoring: ' . $name);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->insertMode = static::IM_BEFORE_HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the start tag.
|
||||
*
|
||||
* @todo - XMLNS namespace handling (we need to parse, even if it's not valid)
|
||||
* - XLink, MathML and SVG namespace handling
|
||||
* - Omission rules: 8.1.2.4 Optional tags
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $attributes
|
||||
* @param bool $selfClosing
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function startTag($name, $attributes = array(), $selfClosing = false)
|
||||
{
|
||||
$lname = $this->normalizeTagName($name);
|
||||
|
||||
// Make sure we have an html element.
|
||||
if (!$this->doc->documentElement && 'html' !== $name && !$this->frag) {
|
||||
$this->startTag('html');
|
||||
}
|
||||
|
||||
// Set quirks mode if we're at IM_INITIAL with no doctype.
|
||||
if ($this->insertMode === static::IM_INITIAL) {
|
||||
$this->quirks = true;
|
||||
$this->parseError('No DOCTYPE specified.');
|
||||
}
|
||||
|
||||
// SPECIAL TAG HANDLING:
|
||||
// Spec says do this, and "don't ask."
|
||||
// find the spec where this is defined... looks problematic
|
||||
if ('image' === $name && !($this->insertMode === static::IM_IN_SVG || $this->insertMode === static::IM_IN_MATHML)) {
|
||||
$name = 'img';
|
||||
}
|
||||
|
||||
// Autoclose p tags where appropriate.
|
||||
if ($this->insertMode >= static::IM_IN_BODY && Elements::isA($name, Elements::AUTOCLOSE_P)) {
|
||||
$this->autoclose('p');
|
||||
}
|
||||
|
||||
// Set insert mode:
|
||||
switch ($name) {
|
||||
case 'html':
|
||||
$this->insertMode = static::IM_BEFORE_HEAD;
|
||||
break;
|
||||
case 'head':
|
||||
if ($this->insertMode > static::IM_BEFORE_HEAD) {
|
||||
$this->parseError('Unexpected head tag outside of head context.');
|
||||
} else {
|
||||
$this->insertMode = static::IM_IN_HEAD;
|
||||
}
|
||||
break;
|
||||
case 'body':
|
||||
$this->insertMode = static::IM_IN_BODY;
|
||||
break;
|
||||
case 'svg':
|
||||
$this->insertMode = static::IM_IN_SVG;
|
||||
break;
|
||||
case 'math':
|
||||
$this->insertMode = static::IM_IN_MATHML;
|
||||
break;
|
||||
case 'noscript':
|
||||
if ($this->insertMode === static::IM_IN_HEAD) {
|
||||
$this->insertMode = static::IM_IN_HEAD_NOSCRIPT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Special case handling for SVG.
|
||||
if ($this->insertMode === static::IM_IN_SVG) {
|
||||
$lname = Elements::normalizeSvgElement($lname);
|
||||
}
|
||||
|
||||
$pushes = 0;
|
||||
// when we found a tag thats appears inside $nsRoots, we have to switch the defalut namespace
|
||||
if (isset($this->nsRoots[$lname]) && $this->nsStack[0][''] !== $this->nsRoots[$lname]) {
|
||||
array_unshift($this->nsStack, array(
|
||||
'' => $this->nsRoots[$lname],
|
||||
) + $this->nsStack[0]);
|
||||
++$pushes;
|
||||
}
|
||||
$needsWorkaround = false;
|
||||
if (isset($this->options['xmlNamespaces']) && $this->options['xmlNamespaces']) {
|
||||
// when xmlNamespaces is true a and we found a 'xmlns' or 'xmlns:*' attribute, we should add a new item to the $nsStack
|
||||
foreach ($attributes as $aName => $aVal) {
|
||||
if ('xmlns' === $aName) {
|
||||
$needsWorkaround = $aVal;
|
||||
array_unshift($this->nsStack, array(
|
||||
'' => $aVal,
|
||||
) + $this->nsStack[0]);
|
||||
++$pushes;
|
||||
} elseif ('xmlns' === (($pos = strpos($aName, ':')) ? substr($aName, 0, $pos) : '')) {
|
||||
array_unshift($this->nsStack, array(
|
||||
substr($aName, $pos + 1) => $aVal,
|
||||
) + $this->nsStack[0]);
|
||||
++$pushes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->onlyInline && Elements::isA($lname, Elements::BLOCK_TAG)) {
|
||||
$this->autoclose($this->onlyInline);
|
||||
$this->onlyInline = null;
|
||||
}
|
||||
|
||||
try {
|
||||
$prefix = ($pos = strpos($lname, ':')) ? substr($lname, 0, $pos) : '';
|
||||
|
||||
if (false !== $needsWorkaround) {
|
||||
$xml = "<$lname xmlns=\"$needsWorkaround\" " . (strlen($prefix) && isset($this->nsStack[0][$prefix]) ? ("xmlns:$prefix=\"" . $this->nsStack[0][$prefix] . '"') : '') . '/>';
|
||||
|
||||
$frag = new \DOMDocument('1.0', 'UTF-8');
|
||||
$frag->loadXML($xml);
|
||||
|
||||
$ele = $this->doc->importNode($frag->documentElement, true);
|
||||
} else {
|
||||
if (!isset($this->nsStack[0][$prefix]) || ('' === $prefix && isset($this->options[self::OPT_DISABLE_HTML_NS]) && $this->options[self::OPT_DISABLE_HTML_NS])) {
|
||||
$ele = $this->doc->createElement($lname);
|
||||
} else {
|
||||
$ele = $this->doc->createElementNS($this->nsStack[0][$prefix], $lname);
|
||||
}
|
||||
}
|
||||
} catch (\DOMException $e) {
|
||||
$this->parseError("Illegal tag name: <$lname>. Replaced with <invalid>.");
|
||||
$ele = $this->doc->createElement('invalid');
|
||||
}
|
||||
|
||||
if (Elements::isA($lname, Elements::BLOCK_ONLY_INLINE)) {
|
||||
$this->onlyInline = $lname;
|
||||
}
|
||||
|
||||
// When we add some namespacess, we have to track them. Later, when "endElement" is invoked, we have to remove them.
|
||||
// When we are on a void tag, we do not need to care about namesapce nesting.
|
||||
if ($pushes > 0 && !Elements::isA($name, Elements::VOID_TAG)) {
|
||||
// PHP tends to free the memory used by DOM,
|
||||
// to avoid spl_object_hash collisions whe have to avoid garbage collection of $ele storing it into $pushes
|
||||
// see https://bugs.php.net/bug.php?id=67459
|
||||
$this->pushes[spl_object_hash($ele)] = array($pushes, $ele);
|
||||
}
|
||||
|
||||
foreach ($attributes as $aName => $aVal) {
|
||||
// xmlns attributes can't be set
|
||||
if ('xmlns' === $aName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->insertMode === static::IM_IN_SVG) {
|
||||
$aName = Elements::normalizeSvgAttribute($aName);
|
||||
} elseif ($this->insertMode === static::IM_IN_MATHML) {
|
||||
$aName = Elements::normalizeMathMlAttribute($aName);
|
||||
}
|
||||
|
||||
$aVal = (string) $aVal;
|
||||
|
||||
try {
|
||||
$prefix = ($pos = strpos($aName, ':')) ? substr($aName, 0, $pos) : false;
|
||||
|
||||
if ('xmlns' === $prefix) {
|
||||
$ele->setAttributeNS(self::NAMESPACE_XMLNS, $aName, $aVal);
|
||||
} elseif (false !== $prefix && isset($this->nsStack[0][$prefix])) {
|
||||
$ele->setAttributeNS($this->nsStack[0][$prefix], $aName, $aVal);
|
||||
} else {
|
||||
$ele->setAttribute($aName, $aVal);
|
||||
}
|
||||
} catch (\DOMException $e) {
|
||||
$this->parseError("Illegal attribute name for tag $name. Ignoring: $aName");
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is necessary on a non-DTD schema, like HTML5.
|
||||
if ('id' === $aName) {
|
||||
$ele->setIdAttribute('id', true);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->frag !== $this->current && $this->rules->hasRules($name)) {
|
||||
// Some elements have special processing rules. Handle those separately.
|
||||
$this->current = $this->rules->evaluate($ele, $this->current);
|
||||
} else {
|
||||
// Otherwise, it's a standard element.
|
||||
$this->current->appendChild($ele);
|
||||
|
||||
if (!Elements::isA($name, Elements::VOID_TAG)) {
|
||||
$this->current = $ele;
|
||||
}
|
||||
|
||||
// Self-closing tags should only be respected on foreign elements
|
||||
// (and are implied on void elements)
|
||||
// See: https://www.w3.org/TR/html5/syntax.html#start-tags
|
||||
if (Elements::isHtml5Element($name)) {
|
||||
$selfClosing = false;
|
||||
}
|
||||
}
|
||||
|
||||
// This is sort of a last-ditch attempt to correct for cases where no head/body
|
||||
// elements are provided.
|
||||
if ($this->insertMode <= static::IM_BEFORE_HEAD && 'head' !== $name && 'html' !== $name) {
|
||||
$this->insertMode = static::IM_IN_BODY;
|
||||
}
|
||||
|
||||
// When we are on a void tag, we do not need to care about namesapce nesting,
|
||||
// but we have to remove the namespaces pushed to $nsStack.
|
||||
if ($pushes > 0 && Elements::isA($name, Elements::VOID_TAG)) {
|
||||
// remove the namespaced definded by current node
|
||||
for ($i = 0; $i < $pushes; ++$i) {
|
||||
array_shift($this->nsStack);
|
||||
}
|
||||
}
|
||||
|
||||
if ($selfClosing) {
|
||||
$this->endTag($name);
|
||||
}
|
||||
|
||||
// Return the element mask, which the tokenizer can then use to set
|
||||
// various processing rules.
|
||||
return Elements::element($name);
|
||||
}
|
||||
|
||||
public function endTag($name)
|
||||
{
|
||||
$lname = $this->normalizeTagName($name);
|
||||
|
||||
// Special case within 12.2.6.4.7: An end tag whose tag name is "br" should be treated as an opening tag
|
||||
if ('br' === $name) {
|
||||
$this->parseError('Closing tag encountered for void element br.');
|
||||
|
||||
$this->startTag('br');
|
||||
}
|
||||
// Ignore closing tags for other unary elements.
|
||||
elseif (Elements::isA($name, Elements::VOID_TAG)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->insertMode <= static::IM_BEFORE_HTML) {
|
||||
// 8.2.5.4.2
|
||||
if (in_array($name, array(
|
||||
'html',
|
||||
'br',
|
||||
'head',
|
||||
'title',
|
||||
))) {
|
||||
$this->startTag('html');
|
||||
$this->endTag($name);
|
||||
$this->insertMode = static::IM_BEFORE_HEAD;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore the tag.
|
||||
$this->parseError('Illegal closing tag at global scope.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case handling for SVG.
|
||||
if ($this->insertMode === static::IM_IN_SVG) {
|
||||
$lname = Elements::normalizeSvgElement($lname);
|
||||
}
|
||||
|
||||
$cid = spl_object_hash($this->current);
|
||||
|
||||
// XXX: HTML has no parent. What do we do, though,
|
||||
// if this element appears in the wrong place?
|
||||
if ('html' === $lname) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove the namespaced definded by current node
|
||||
if (isset($this->pushes[$cid])) {
|
||||
for ($i = 0; $i < $this->pushes[$cid][0]; ++$i) {
|
||||
array_shift($this->nsStack);
|
||||
}
|
||||
unset($this->pushes[$cid]);
|
||||
}
|
||||
|
||||
if (!$this->autoclose($lname)) {
|
||||
$this->parseError('Could not find closing tag for ' . $lname);
|
||||
}
|
||||
|
||||
switch ($lname) {
|
||||
case 'head':
|
||||
$this->insertMode = static::IM_AFTER_HEAD;
|
||||
break;
|
||||
case 'body':
|
||||
$this->insertMode = static::IM_AFTER_BODY;
|
||||
break;
|
||||
case 'svg':
|
||||
case 'mathml':
|
||||
$this->insertMode = static::IM_IN_BODY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function comment($cdata)
|
||||
{
|
||||
// TODO: Need to handle case where comment appears outside of the HTML tag.
|
||||
$node = $this->doc->createComment($cdata);
|
||||
$this->current->appendChild($node);
|
||||
}
|
||||
|
||||
public function text($data)
|
||||
{
|
||||
// XXX: Hmmm.... should we really be this strict?
|
||||
if ($this->insertMode < static::IM_IN_HEAD) {
|
||||
// Per '8.2.5.4.3 The "before head" insertion mode' the characters
|
||||
// " \t\n\r\f" should be ignored but no mention of a parse error. This is
|
||||
// practical as most documents contain these characters. Other text is not
|
||||
// expected here so recording a parse error is necessary.
|
||||
$dataTmp = trim($data, " \t\n\r\f");
|
||||
if (!empty($dataTmp)) {
|
||||
// fprintf(STDOUT, "Unexpected insert mode: %d", $this->insertMode);
|
||||
$this->parseError('Unexpected text. Ignoring: ' . $dataTmp);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
// fprintf(STDOUT, "Appending text %s.", $data);
|
||||
$node = $this->doc->createTextNode($data);
|
||||
$this->current->appendChild($node);
|
||||
}
|
||||
|
||||
public function eof()
|
||||
{
|
||||
// If the $current isn't the $root, do we need to do anything?
|
||||
}
|
||||
|
||||
public function parseError($msg, $line = 0, $col = 0)
|
||||
{
|
||||
$this->errors[] = sprintf('Line %d, Col %d: %s', $line, $col, $msg);
|
||||
}
|
||||
|
||||
public function getErrors()
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
public function cdata($data)
|
||||
{
|
||||
$node = $this->doc->createCDATASection($data);
|
||||
$this->current->appendChild($node);
|
||||
}
|
||||
|
||||
public function processingInstruction($name, $data = null)
|
||||
{
|
||||
// XXX: Ignore initial XML declaration, per the spec.
|
||||
if ($this->insertMode === static::IM_INITIAL && 'xml' === strtolower($name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Important: The processor may modify the current DOM tree however it sees fit.
|
||||
if ($this->processor instanceof InstructionProcessor) {
|
||||
$res = $this->processor->process($this->current, $name, $data);
|
||||
if (!empty($res)) {
|
||||
$this->current = $res;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, this is just a dumb PI element.
|
||||
$node = $this->doc->createProcessingInstruction($name, $data);
|
||||
|
||||
$this->current->appendChild($node);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// UTILITIES
|
||||
// ==========================================================================
|
||||
|
||||
/**
|
||||
* Apply normalization rules to a tag name.
|
||||
* See sections 2.9 and 8.1.2.
|
||||
*
|
||||
* @param string $tagName
|
||||
*
|
||||
* @return string The normalized tag name.
|
||||
*/
|
||||
protected function normalizeTagName($tagName)
|
||||
{
|
||||
/*
|
||||
* Section 2.9 suggests that we should not do this. if (strpos($name, ':') !== false) { // We know from the grammar that there must be at least one other // char besides :, since : is not a legal tag start. $parts = explode(':', $name); return array_pop($parts); }
|
||||
*/
|
||||
return $tagName;
|
||||
}
|
||||
|
||||
protected function quirksTreeResolver($name)
|
||||
{
|
||||
throw new \Exception('Not implemented.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically climb the tree and close the closest node with the matching $tag.
|
||||
*
|
||||
* @param string $tagName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function autoclose($tagName)
|
||||
{
|
||||
$working = $this->current;
|
||||
do {
|
||||
if (XML_ELEMENT_NODE !== $working->nodeType) {
|
||||
return false;
|
||||
}
|
||||
if ($working->tagName === $tagName) {
|
||||
$this->current = $working->parentNode;
|
||||
|
||||
return true;
|
||||
}
|
||||
} while ($working = $working->parentNode);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given tagname is an ancestor of the present candidate.
|
||||
*
|
||||
* If $this->current or anything above $this->current matches the given tag
|
||||
* name, this returns true.
|
||||
*
|
||||
* @param string $tagName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAncestor($tagName)
|
||||
{
|
||||
$candidate = $this->current;
|
||||
while (XML_ELEMENT_NODE === $candidate->nodeType) {
|
||||
if ($candidate->tagName === $tagName) {
|
||||
return true;
|
||||
}
|
||||
$candidate = $candidate->parentNode;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the immediate parent element is of the given tagname.
|
||||
*
|
||||
* @param string $tagName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isParent($tagName)
|
||||
{
|
||||
return $this->current->tagName === $tagName;
|
||||
}
|
||||
}
|
||||
114
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/EventHandler.php
vendored
Normal file
114
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/EventHandler.php
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Masterminds\HTML5\Parser;
|
||||
|
||||
/**
|
||||
* Standard events for HTML5.
|
||||
*
|
||||
* This is roughly analogous to a SAX2 or expat-style interface.
|
||||
* However, it is tuned specifically for HTML5, according to section 8
|
||||
* of the HTML5 specification.
|
||||
*
|
||||
* An event handler receives parser events. For a concrete
|
||||
* implementation, see DOMTreeBuilder.
|
||||
*
|
||||
* Quirks support in the parser is limited to close-in syntax (malformed
|
||||
* tags or attributes). Higher order syntax and semantic issues with a
|
||||
* document (e.g. mismatched tags, illegal nesting, etc.) are the
|
||||
* responsibility of the event handler implementation.
|
||||
*
|
||||
* See HTML5 spec section 8.2.4
|
||||
*/
|
||||
interface EventHandler
|
||||
{
|
||||
const DOCTYPE_NONE = 0;
|
||||
|
||||
const DOCTYPE_PUBLIC = 1;
|
||||
|
||||
const DOCTYPE_SYSTEM = 2;
|
||||
|
||||
/**
|
||||
* A doctype declaration.
|
||||
*
|
||||
* @param string $name The name of the root element.
|
||||
* @param int $idType One of DOCTYPE_NONE, DOCTYPE_PUBLIC, or DOCTYPE_SYSTEM
|
||||
* @param string $id The identifier. For DOCTYPE_PUBLIC, this is the public ID. If DOCTYPE_SYSTEM,
|
||||
* then this is a system ID.
|
||||
* @param bool $quirks Indicates whether the builder should enter quirks mode.
|
||||
*/
|
||||
public function doctype($name, $idType = 0, $id = null, $quirks = false);
|
||||
|
||||
/**
|
||||
* A start tag.
|
||||
*
|
||||
* IMPORTANT: The parser watches the return value of this event. If this returns
|
||||
* an integer, the parser will switch TEXTMODE patters according to the int.
|
||||
*
|
||||
* This is how the Tree Builder can tell the Tokenizer when a certain tag should
|
||||
* cause the parser to go into RAW text mode.
|
||||
*
|
||||
* The HTML5 standard requires that the builder is the one that initiates this
|
||||
* step, and this is the only way short of a circular reference that we can
|
||||
* do that.
|
||||
*
|
||||
* Example: if a startTag even for a `script` name is fired, and the startTag()
|
||||
* implementation returns Tokenizer::TEXTMODE_RAW, then the tokenizer will
|
||||
* switch into RAW text mode and consume data until it reaches a closing
|
||||
* `script` tag.
|
||||
*
|
||||
* The textmode is automatically reset to Tokenizer::TEXTMODE_NORMAL when the
|
||||
* closing tag is encounter. **This behavior may change.**
|
||||
*
|
||||
* @param string $name The tag name.
|
||||
* @param array $attributes An array with all of the tag's attributes.
|
||||
* @param bool $selfClosing An indicator of whether or not this tag is self-closing (<foo/>).
|
||||
*
|
||||
* @return int one of the Tokenizer::TEXTMODE_* constants
|
||||
*/
|
||||
public function startTag($name, $attributes = array(), $selfClosing = false);
|
||||
|
||||
/**
|
||||
* An end-tag.
|
||||
*/
|
||||
public function endTag($name);
|
||||
|
||||
/**
|
||||
* A comment section (unparsed character data).
|
||||
*/
|
||||
public function comment($cdata);
|
||||
|
||||
/**
|
||||
* A unit of parsed character data.
|
||||
*
|
||||
* Entities in this text are *already decoded*.
|
||||
*/
|
||||
public function text($cdata);
|
||||
|
||||
/**
|
||||
* Indicates that the document has been entirely processed.
|
||||
*/
|
||||
public function eof();
|
||||
|
||||
/**
|
||||
* Emitted when the parser encounters an error condition.
|
||||
*/
|
||||
public function parseError($msg, $line, $col);
|
||||
|
||||
/**
|
||||
* A CDATA section.
|
||||
*
|
||||
* @param string $data
|
||||
* The unparsed character data
|
||||
*/
|
||||
public function cdata($data);
|
||||
|
||||
/**
|
||||
* This is a holdover from the XML spec.
|
||||
*
|
||||
* While user agents don't get PIs, server-side does.
|
||||
*
|
||||
* @param string $name The name of the processor (e.g. 'php').
|
||||
* @param string $data The unparsed data.
|
||||
*/
|
||||
public function processingInstruction($name, $data = null);
|
||||
}
|
||||
33
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/FileInputStream.php
vendored
Normal file
33
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/FileInputStream.php
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Masterminds\HTML5\Parser;
|
||||
|
||||
/**
|
||||
* The FileInputStream loads a file to be parsed.
|
||||
*
|
||||
* So right now we read files into strings and then process the
|
||||
* string. We chose to do this largely for the sake of expediency of
|
||||
* development, and also because we could optimize toward processing
|
||||
* arbitrarily large chunks of the input. But in the future, we'd
|
||||
* really like to rewrite this class to efficiently handle lower level
|
||||
* stream reads (and thus efficiently handle large documents).
|
||||
*
|
||||
* @deprecated since 2.4, to remove in 3.0. Use a string in the scanner instead.
|
||||
*/
|
||||
class FileInputStream extends StringInputStream implements InputStream
|
||||
{
|
||||
/**
|
||||
* Load a file input stream.
|
||||
*
|
||||
* @param string $data The file or url path to load.
|
||||
* @param string $encoding The encoding to use for the data.
|
||||
* @param string $debug A fprintf format to use to echo the data on stdout.
|
||||
*/
|
||||
public function __construct($data, $encoding = 'UTF-8', $debug = '')
|
||||
{
|
||||
// Get the contents of the file.
|
||||
$content = file_get_contents($data);
|
||||
|
||||
parent::__construct($content, $encoding, $debug);
|
||||
}
|
||||
}
|
||||
87
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/InputStream.php
vendored
Normal file
87
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/InputStream.php
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Masterminds\HTML5\Parser;
|
||||
|
||||
/**
|
||||
* Interface for stream readers.
|
||||
*
|
||||
* The parser only reads from streams. Various input sources can write
|
||||
* an adapater to this InputStream.
|
||||
*
|
||||
* Currently provided InputStream implementations include
|
||||
* FileInputStream and StringInputStream.
|
||||
*
|
||||
* @deprecated since 2.4, to remove in 3.0. Use a string in the scanner instead.
|
||||
*/
|
||||
interface InputStream extends \Iterator
|
||||
{
|
||||
/**
|
||||
* Returns the current line that is being consumed.
|
||||
*
|
||||
* TODO: Move this to the scanner.
|
||||
*/
|
||||
public function currentLine();
|
||||
|
||||
/**
|
||||
* Returns the current column of the current line that the tokenizer is at.
|
||||
*
|
||||
* Newlines are column 0. The first char after a newline is column 1.
|
||||
*
|
||||
* @TODO Move this to the scanner.
|
||||
*
|
||||
* @return int The column number.
|
||||
*/
|
||||
public function columnOffset();
|
||||
|
||||
/**
|
||||
* Get all characters until EOF.
|
||||
*
|
||||
* This consumes characters until the EOF.
|
||||
*/
|
||||
public function remainingChars();
|
||||
|
||||
/**
|
||||
* Read to a particular match (or until $max bytes are consumed).
|
||||
*
|
||||
* This operates on byte sequences, not characters.
|
||||
*
|
||||
* Matches as far as possible until we reach a certain set of bytes
|
||||
* and returns the matched substring.
|
||||
*
|
||||
* @see strcspn
|
||||
*
|
||||
* @param string $bytes Bytes to match.
|
||||
* @param int $max Maximum number of bytes to scan.
|
||||
*
|
||||
* @return mixed Index or false if no match is found. You should use strong
|
||||
* equality when checking the result, since index could be 0.
|
||||
*/
|
||||
public function charsUntil($bytes, $max = null);
|
||||
|
||||
/**
|
||||
* Returns the string so long as $bytes matches.
|
||||
*
|
||||
* Matches as far as possible with a certain set of bytes
|
||||
* and returns the matched substring.
|
||||
*
|
||||
* @see strspn
|
||||
*
|
||||
* @param string $bytes A mask of bytes to match. If ANY byte in this mask matches the
|
||||
* current char, the pointer advances and the char is part of the
|
||||
* substring.
|
||||
* @param int $max The max number of chars to read.
|
||||
*/
|
||||
public function charsWhile($bytes, $max = null);
|
||||
|
||||
/**
|
||||
* Unconsume one character.
|
||||
*
|
||||
* @param int $howMany The number of characters to move the pointer back.
|
||||
*/
|
||||
public function unconsume($howMany = 1);
|
||||
|
||||
/**
|
||||
* Retrieve the next character without advancing the pointer.
|
||||
*/
|
||||
public function peek();
|
||||
}
|
||||
10
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/ParseError.php
vendored
Normal file
10
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/ParseError.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Masterminds\HTML5\Parser;
|
||||
|
||||
/**
|
||||
* Emit when the parser has an error.
|
||||
*/
|
||||
class ParseError extends \Exception
|
||||
{
|
||||
}
|
||||
53
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/README.md
vendored
Normal file
53
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/README.md
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
# The Parser Model
|
||||
|
||||
The parser model here follows the model in section
|
||||
[8.2.1](http://www.w3.org/TR/2012/CR-html5-20121217/syntax.html#parsing)
|
||||
of the HTML5 specification, though we do not assume a networking layer.
|
||||
|
||||
[ InputStream ] // Generic support for reading input.
|
||||
||
|
||||
[ Scanner ] // Breaks down the stream into characters.
|
||||
||
|
||||
[ Tokenizer ] // Groups characters into syntactic
|
||||
||
|
||||
[ Tree Builder ] // Organizes units into a tree of objects
|
||||
||
|
||||
[ DOM Document ] // The final state of the parsed document.
|
||||
|
||||
|
||||
## InputStream
|
||||
|
||||
This is an interface with at least two concrete implementations:
|
||||
|
||||
- StringInputStream: Reads an HTML5 string.
|
||||
- FileInputStream: Reads an HTML5 file.
|
||||
|
||||
## Scanner
|
||||
|
||||
This is a mechanical piece of the parser.
|
||||
|
||||
## Tokenizer
|
||||
|
||||
This follows section 8.4 of the HTML5 spec. It is (roughly) a recursive
|
||||
descent parser. (Though there are plenty of optimizations that are less
|
||||
than purely functional.
|
||||
|
||||
## EventHandler and DOMTree
|
||||
|
||||
EventHandler is the interface for tree builders. Since not all
|
||||
implementations will necessarily build trees, we've chosen a more
|
||||
generic name.
|
||||
|
||||
The event handler emits tokens during tokenization.
|
||||
|
||||
The DOMTree is an event handler that builds a DOM tree. The output of
|
||||
the DOMTree builder is a DOMDocument.
|
||||
|
||||
## DOMDocument
|
||||
|
||||
PHP has a DOMDocument class built-in (technically, it's part of libxml.)
|
||||
We use that, thus rendering the output of this process compatible with
|
||||
SimpleXML, QueryPath, and many other XML/HTML processing tools.
|
||||
|
||||
For cases where the HTML5 is a fragment of a HTML5 document a
|
||||
DOMDocumentFragment is returned instead. This is another built-in class.
|
||||
416
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/Scanner.php
vendored
Normal file
416
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/Scanner.php
vendored
Normal file
@@ -0,0 +1,416 @@
|
||||
<?php
|
||||
|
||||
namespace Masterminds\HTML5\Parser;
|
||||
|
||||
use Masterminds\HTML5\Exception;
|
||||
|
||||
/**
|
||||
* The scanner scans over a given data input to react appropriately to characters.
|
||||
*/
|
||||
class Scanner
|
||||
{
|
||||
const CHARS_HEX = 'abcdefABCDEF01234567890';
|
||||
const CHARS_ALNUM = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890';
|
||||
const CHARS_ALPHA = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
/**
|
||||
* The string data we're parsing.
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* The current integer byte position we are in $data.
|
||||
*/
|
||||
private $char;
|
||||
|
||||
/**
|
||||
* Length of $data; when $char === $data, we are at the end-of-file.
|
||||
*/
|
||||
private $EOF;
|
||||
|
||||
/**
|
||||
* Parse errors.
|
||||
*/
|
||||
public $errors = array();
|
||||
|
||||
/**
|
||||
* Create a new Scanner.
|
||||
*
|
||||
* @param string $data Data to parse.
|
||||
* @param string $encoding The encoding to use for the data.
|
||||
*
|
||||
* @throws Exception If the given data cannot be encoded to UTF-8.
|
||||
*/
|
||||
public function __construct($data, $encoding = 'UTF-8')
|
||||
{
|
||||
if ($data instanceof InputStream) {
|
||||
@trigger_error('InputStream objects are deprecated since version 2.4 and will be removed in 3.0. Use strings instead.', E_USER_DEPRECATED);
|
||||
$data = (string) $data;
|
||||
}
|
||||
|
||||
$data = UTF8Utils::convertToUTF8($data, $encoding);
|
||||
|
||||
// There is good reason to question whether it makes sense to
|
||||
// do this here, since most of these checks are done during
|
||||
// parsing, and since this check doesn't actually *do* anything.
|
||||
$this->errors = UTF8Utils::checkForIllegalCodepoints($data);
|
||||
|
||||
$data = $this->replaceLinefeeds($data);
|
||||
|
||||
$this->data = $data;
|
||||
$this->char = 0;
|
||||
$this->EOF = strlen($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if upcomming chars match the given sequence.
|
||||
*
|
||||
* This will read the stream for the $sequence. If it's
|
||||
* found, this will return true. If not, return false.
|
||||
* Since this unconsumes any chars it reads, the caller
|
||||
* will still need to read the next sequence, even if
|
||||
* this returns true.
|
||||
*
|
||||
* Example: $this->scanner->sequenceMatches('</script>') will
|
||||
* see if the input stream is at the start of a
|
||||
* '</script>' string.
|
||||
*
|
||||
* @param string $sequence
|
||||
* @param bool $caseSensitive
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sequenceMatches($sequence, $caseSensitive = true)
|
||||
{
|
||||
$portion = substr($this->data, $this->char, strlen($sequence));
|
||||
|
||||
return $caseSensitive ? $portion === $sequence : 0 === strcasecmp($portion, $sequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current position.
|
||||
*
|
||||
* @return int The current intiger byte position.
|
||||
*/
|
||||
public function position()
|
||||
{
|
||||
return $this->char;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a peek at the next character in the data.
|
||||
*
|
||||
* @return string The next character.
|
||||
*/
|
||||
public function peek()
|
||||
{
|
||||
if (($this->char + 1) < $this->EOF) {
|
||||
return $this->data[$this->char + 1];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next character.
|
||||
* Note: This advances the pointer.
|
||||
*
|
||||
* @return string The next character.
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
++$this->char;
|
||||
|
||||
if ($this->char < $this->EOF) {
|
||||
return $this->data[$this->char];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current character.
|
||||
* Note, this does not advance the pointer.
|
||||
*
|
||||
* @return string The current character.
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
if ($this->char < $this->EOF) {
|
||||
return $this->data[$this->char];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Silently consume N chars.
|
||||
*
|
||||
* @param int $count
|
||||
*/
|
||||
public function consume($count = 1)
|
||||
{
|
||||
$this->char += $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unconsume some of the data.
|
||||
* This moves the data pointer backwards.
|
||||
*
|
||||
* @param int $howMany The number of characters to move the pointer back.
|
||||
*/
|
||||
public function unconsume($howMany = 1)
|
||||
{
|
||||
if (($this->char - $howMany) >= 0) {
|
||||
$this->char -= $howMany;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next group of that contains hex characters.
|
||||
* Note, along with getting the characters the pointer in the data will be
|
||||
* moved as well.
|
||||
*
|
||||
* @return string The next group that is hex characters.
|
||||
*/
|
||||
public function getHex()
|
||||
{
|
||||
return $this->doCharsWhile(static::CHARS_HEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next group of characters that are ASCII Alpha characters.
|
||||
* Note, along with getting the characters the pointer in the data will be
|
||||
* moved as well.
|
||||
*
|
||||
* @return string The next group of ASCII alpha characters.
|
||||
*/
|
||||
public function getAsciiAlpha()
|
||||
{
|
||||
return $this->doCharsWhile(static::CHARS_ALPHA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next group of characters that are ASCII Alpha characters and numbers.
|
||||
* Note, along with getting the characters the pointer in the data will be
|
||||
* moved as well.
|
||||
*
|
||||
* @return string The next group of ASCII alpha characters and numbers.
|
||||
*/
|
||||
public function getAsciiAlphaNum()
|
||||
{
|
||||
return $this->doCharsWhile(static::CHARS_ALNUM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next group of numbers.
|
||||
* Note, along with getting the characters the pointer in the data will be
|
||||
* moved as well.
|
||||
*
|
||||
* @return string The next group of numbers.
|
||||
*/
|
||||
public function getNumeric()
|
||||
{
|
||||
return $this->doCharsWhile('0123456789');
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume whitespace.
|
||||
* Whitespace in HTML5 is: formfeed, tab, newline, space.
|
||||
*
|
||||
* @return int The length of the matched whitespaces.
|
||||
*/
|
||||
public function whitespace()
|
||||
{
|
||||
if ($this->char >= $this->EOF) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$len = strspn($this->data, "\n\t\f ", $this->char);
|
||||
|
||||
$this->char += $len;
|
||||
|
||||
return $len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current line that is being consumed.
|
||||
*
|
||||
* @return int The current line number.
|
||||
*/
|
||||
public function currentLine()
|
||||
{
|
||||
if (empty($this->EOF) || 0 === $this->char) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Add one to $this->char because we want the number for the next
|
||||
// byte to be processed.
|
||||
return substr_count($this->data, "\n", 0, min($this->char, $this->EOF)) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read chars until something in the mask is encountered.
|
||||
*
|
||||
* @param string $mask
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function charsUntil($mask)
|
||||
{
|
||||
return $this->doCharsUntil($mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read chars as long as the mask matches.
|
||||
*
|
||||
* @param string $mask
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function charsWhile($mask)
|
||||
{
|
||||
return $this->doCharsWhile($mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current column of the current line that the tokenizer is at.
|
||||
*
|
||||
* Newlines are column 0. The first char after a newline is column 1.
|
||||
*
|
||||
* @return int The column number.
|
||||
*/
|
||||
public function columnOffset()
|
||||
{
|
||||
// Short circuit for the first char.
|
||||
if (0 === $this->char) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// strrpos is weird, and the offset needs to be negative for what we
|
||||
// want (i.e., the last \n before $this->char). This needs to not have
|
||||
// one (to make it point to the next character, the one we want the
|
||||
// position of) added to it because strrpos's behaviour includes the
|
||||
// final offset byte.
|
||||
$backwardFrom = $this->char - 1 - strlen($this->data);
|
||||
$lastLine = strrpos($this->data, "\n", $backwardFrom);
|
||||
|
||||
// However, for here we want the length up until the next byte to be
|
||||
// processed, so add one to the current byte ($this->char).
|
||||
if (false !== $lastLine) {
|
||||
$findLengthOf = substr($this->data, $lastLine + 1, $this->char - 1 - $lastLine);
|
||||
} else {
|
||||
// After a newline.
|
||||
$findLengthOf = substr($this->data, 0, $this->char);
|
||||
}
|
||||
|
||||
return UTF8Utils::countChars($findLengthOf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all characters until EOF.
|
||||
*
|
||||
* This consumes characters until the EOF.
|
||||
*
|
||||
* @return int The number of characters remaining.
|
||||
*/
|
||||
public function remainingChars()
|
||||
{
|
||||
if ($this->char < $this->EOF) {
|
||||
$data = substr($this->data, $this->char);
|
||||
$this->char = $this->EOF;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
return ''; // false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace linefeed characters according to the spec.
|
||||
*
|
||||
* @param $data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function replaceLinefeeds($data)
|
||||
{
|
||||
/*
|
||||
* U+000D CARRIAGE RETURN (CR) characters and U+000A LINE FEED (LF) characters are treated specially.
|
||||
* Any CR characters that are followed by LF characters must be removed, and any CR characters not
|
||||
* followed by LF characters must be converted to LF characters. Thus, newlines in HTML DOMs are
|
||||
* represented by LF characters, and there are never any CR characters in the input to the tokenization
|
||||
* stage.
|
||||
*/
|
||||
$crlfTable = array(
|
||||
"\0" => "\xEF\xBF\xBD",
|
||||
"\r\n" => "\n",
|
||||
"\r" => "\n",
|
||||
);
|
||||
|
||||
return strtr($data, $crlfTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read to a particular match (or until $max bytes are consumed).
|
||||
*
|
||||
* This operates on byte sequences, not characters.
|
||||
*
|
||||
* Matches as far as possible until we reach a certain set of bytes
|
||||
* and returns the matched substring.
|
||||
*
|
||||
* @param string $bytes Bytes to match.
|
||||
* @param int $max Maximum number of bytes to scan.
|
||||
*
|
||||
* @return mixed Index or false if no match is found. You should use strong
|
||||
* equality when checking the result, since index could be 0.
|
||||
*/
|
||||
private function doCharsUntil($bytes, $max = null)
|
||||
{
|
||||
if ($this->char >= $this->EOF) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 === $max || $max) {
|
||||
$len = strcspn($this->data, $bytes, $this->char, $max);
|
||||
} else {
|
||||
$len = strcspn($this->data, $bytes, $this->char);
|
||||
}
|
||||
|
||||
$string = (string) substr($this->data, $this->char, $len);
|
||||
$this->char += $len;
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string so long as $bytes matches.
|
||||
*
|
||||
* Matches as far as possible with a certain set of bytes
|
||||
* and returns the matched substring.
|
||||
*
|
||||
* @param string $bytes A mask of bytes to match. If ANY byte in this mask matches the
|
||||
* current char, the pointer advances and the char is part of the
|
||||
* substring.
|
||||
* @param int $max The max number of chars to read.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function doCharsWhile($bytes, $max = null)
|
||||
{
|
||||
if ($this->char >= $this->EOF) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 === $max || $max) {
|
||||
$len = strspn($this->data, $bytes, $this->char, $max);
|
||||
} else {
|
||||
$len = strspn($this->data, $bytes, $this->char);
|
||||
}
|
||||
|
||||
$string = (string) substr($this->data, $this->char, $len);
|
||||
$this->char += $len;
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
331
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/StringInputStream.php
vendored
Normal file
331
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/StringInputStream.php
vendored
Normal file
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
/**
|
||||
* Loads a string to be parsed.
|
||||
*/
|
||||
|
||||
namespace Masterminds\HTML5\Parser;
|
||||
|
||||
/*
|
||||
*
|
||||
* Based on code from html5lib:
|
||||
|
||||
Copyright 2009 Geoffrey Sneddon <http://gsnedders.com/>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
// Some conventions:
|
||||
// - /* */ indicates verbatim text from the HTML 5 specification
|
||||
// MPB: Not sure which version of the spec. Moving from HTML5lib to
|
||||
// HTML5-PHP, I have been using this version:
|
||||
// http://www.w3.org/TR/2012/CR-html5-20121217/Overview.html#contents
|
||||
//
|
||||
// - // indicates regular comments
|
||||
|
||||
/**
|
||||
* @deprecated since 2.4, to remove in 3.0. Use a string in the scanner instead.
|
||||
*/
|
||||
class StringInputStream implements InputStream
|
||||
{
|
||||
/**
|
||||
* The string data we're parsing.
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* The current integer byte position we are in $data.
|
||||
*/
|
||||
private $char;
|
||||
|
||||
/**
|
||||
* Length of $data; when $char === $data, we are at the end-of-file.
|
||||
*/
|
||||
private $EOF;
|
||||
|
||||
/**
|
||||
* Parse errors.
|
||||
*/
|
||||
public $errors = array();
|
||||
|
||||
/**
|
||||
* Create a new InputStream wrapper.
|
||||
*
|
||||
* @param string $data Data to parse.
|
||||
* @param string $encoding The encoding to use for the data.
|
||||
* @param string $debug A fprintf format to use to echo the data on stdout.
|
||||
*/
|
||||
public function __construct($data, $encoding = 'UTF-8', $debug = '')
|
||||
{
|
||||
$data = UTF8Utils::convertToUTF8($data, $encoding);
|
||||
if ($debug) {
|
||||
fprintf(STDOUT, $debug, $data, strlen($data));
|
||||
}
|
||||
|
||||
// There is good reason to question whether it makes sense to
|
||||
// do this here, since most of these checks are done during
|
||||
// parsing, and since this check doesn't actually *do* anything.
|
||||
$this->errors = UTF8Utils::checkForIllegalCodepoints($data);
|
||||
|
||||
$data = $this->replaceLinefeeds($data);
|
||||
|
||||
$this->data = $data;
|
||||
$this->char = 0;
|
||||
$this->EOF = strlen($data);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace linefeed characters according to the spec.
|
||||
*/
|
||||
protected function replaceLinefeeds($data)
|
||||
{
|
||||
/*
|
||||
* U+000D CARRIAGE RETURN (CR) characters and U+000A LINE FEED (LF) characters are treated specially.
|
||||
* Any CR characters that are followed by LF characters must be removed, and any CR characters not
|
||||
* followed by LF characters must be converted to LF characters. Thus, newlines in HTML DOMs are
|
||||
* represented by LF characters, and there are never any CR characters in the input to the tokenization
|
||||
* stage.
|
||||
*/
|
||||
$crlfTable = array(
|
||||
"\0" => "\xEF\xBF\xBD",
|
||||
"\r\n" => "\n",
|
||||
"\r" => "\n",
|
||||
);
|
||||
|
||||
return strtr($data, $crlfTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current line that the tokenizer is at.
|
||||
*/
|
||||
public function currentLine()
|
||||
{
|
||||
if (empty($this->EOF) || 0 === $this->char) {
|
||||
return 1;
|
||||
}
|
||||
// Add one to $this->char because we want the number for the next
|
||||
// byte to be processed.
|
||||
return substr_count($this->data, "\n", 0, min($this->char, $this->EOF)) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function getCurrentLine()
|
||||
{
|
||||
return $this->currentLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current column of the current line that the tokenizer is at.
|
||||
* Newlines are column 0. The first char after a newline is column 1.
|
||||
*
|
||||
* @return int The column number.
|
||||
*/
|
||||
public function columnOffset()
|
||||
{
|
||||
// Short circuit for the first char.
|
||||
if (0 === $this->char) {
|
||||
return 0;
|
||||
}
|
||||
// strrpos is weird, and the offset needs to be negative for what we
|
||||
// want (i.e., the last \n before $this->char). This needs to not have
|
||||
// one (to make it point to the next character, the one we want the
|
||||
// position of) added to it because strrpos's behaviour includes the
|
||||
// final offset byte.
|
||||
$backwardFrom = $this->char - 1 - strlen($this->data);
|
||||
$lastLine = strrpos($this->data, "\n", $backwardFrom);
|
||||
|
||||
// However, for here we want the length up until the next byte to be
|
||||
// processed, so add one to the current byte ($this->char).
|
||||
if (false !== $lastLine) {
|
||||
$findLengthOf = substr($this->data, $lastLine + 1, $this->char - 1 - $lastLine);
|
||||
} else {
|
||||
// After a newline.
|
||||
$findLengthOf = substr($this->data, 0, $this->char);
|
||||
}
|
||||
|
||||
return UTF8Utils::countChars($findLengthOf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function getColumnOffset()
|
||||
{
|
||||
return $this->columnOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current character.
|
||||
*
|
||||
* @return string The current character.
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->data[$this->char];
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance the pointer.
|
||||
* This is part of the Iterator interface.
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
++$this->char;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind to the start of the string.
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->char = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the current pointer location valid.
|
||||
*
|
||||
* @return bool Whether the current pointer location is valid.
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return $this->char < $this->EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all characters until EOF.
|
||||
*
|
||||
* This reads to the end of the file, and sets the read marker at the
|
||||
* end of the file.
|
||||
*
|
||||
* Note this performs bounds checking.
|
||||
*
|
||||
* @return string Returns the remaining text. If called when the InputStream is
|
||||
* already exhausted, it returns an empty string.
|
||||
*/
|
||||
public function remainingChars()
|
||||
{
|
||||
if ($this->char < $this->EOF) {
|
||||
$data = substr($this->data, $this->char);
|
||||
$this->char = $this->EOF;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
return ''; // false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read to a particular match (or until $max bytes are consumed).
|
||||
*
|
||||
* This operates on byte sequences, not characters.
|
||||
*
|
||||
* Matches as far as possible until we reach a certain set of bytes
|
||||
* and returns the matched substring.
|
||||
*
|
||||
* @param string $bytes Bytes to match.
|
||||
* @param int $max Maximum number of bytes to scan.
|
||||
*
|
||||
* @return mixed Index or false if no match is found. You should use strong
|
||||
* equality when checking the result, since index could be 0.
|
||||
*/
|
||||
public function charsUntil($bytes, $max = null)
|
||||
{
|
||||
if ($this->char >= $this->EOF) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 === $max || $max) {
|
||||
$len = strcspn($this->data, $bytes, $this->char, $max);
|
||||
} else {
|
||||
$len = strcspn($this->data, $bytes, $this->char);
|
||||
}
|
||||
|
||||
$string = (string) substr($this->data, $this->char, $len);
|
||||
$this->char += $len;
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string so long as $bytes matches.
|
||||
*
|
||||
* Matches as far as possible with a certain set of bytes
|
||||
* and returns the matched substring.
|
||||
*
|
||||
* @param string $bytes A mask of bytes to match. If ANY byte in this mask matches the
|
||||
* current char, the pointer advances and the char is part of the
|
||||
* substring.
|
||||
* @param int $max The max number of chars to read.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function charsWhile($bytes, $max = null)
|
||||
{
|
||||
if ($this->char >= $this->EOF) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 === $max || $max) {
|
||||
$len = strspn($this->data, $bytes, $this->char, $max);
|
||||
} else {
|
||||
$len = strspn($this->data, $bytes, $this->char);
|
||||
}
|
||||
$string = (string) substr($this->data, $this->char, $len);
|
||||
$this->char += $len;
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unconsume characters.
|
||||
*
|
||||
* @param int $howMany The number of characters to unconsume.
|
||||
*/
|
||||
public function unconsume($howMany = 1)
|
||||
{
|
||||
if (($this->char - $howMany) >= 0) {
|
||||
$this->char -= $howMany;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look ahead without moving cursor.
|
||||
*/
|
||||
public function peek()
|
||||
{
|
||||
if (($this->char + 1) <= $this->EOF) {
|
||||
return $this->data[$this->char + 1];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function key()
|
||||
{
|
||||
return $this->char;
|
||||
}
|
||||
}
|
||||
1197
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/Tokenizer.php
vendored
Normal file
1197
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/Tokenizer.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
127
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/TreeBuildingRules.php
vendored
Normal file
127
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/TreeBuildingRules.php
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace Masterminds\HTML5\Parser;
|
||||
|
||||
/**
|
||||
* Handles special-case rules for the DOM tree builder.
|
||||
*
|
||||
* Many tags have special rules that need to be accomodated on an
|
||||
* individual basis. This class handles those rules.
|
||||
*
|
||||
* See section 8.1.2.4 of the spec.
|
||||
*
|
||||
* @todo - colgroup and col special behaviors
|
||||
* - body and head special behaviors
|
||||
*/
|
||||
class TreeBuildingRules
|
||||
{
|
||||
protected static $tags = array(
|
||||
'li' => 1,
|
||||
'dd' => 1,
|
||||
'dt' => 1,
|
||||
'rt' => 1,
|
||||
'rp' => 1,
|
||||
'tr' => 1,
|
||||
'th' => 1,
|
||||
'td' => 1,
|
||||
'thead' => 1,
|
||||
'tfoot' => 1,
|
||||
'tbody' => 1,
|
||||
'table' => 1,
|
||||
'optgroup' => 1,
|
||||
'option' => 1,
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns true if the given tagname has special processing rules.
|
||||
*/
|
||||
public function hasRules($tagname)
|
||||
{
|
||||
return isset(static::$tags[$tagname]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the rule for the current tag name.
|
||||
*
|
||||
* This may modify the existing DOM.
|
||||
*
|
||||
* @return \DOMElement The new Current DOM element.
|
||||
*/
|
||||
public function evaluate($new, $current)
|
||||
{
|
||||
switch ($new->tagName) {
|
||||
case 'li':
|
||||
return $this->handleLI($new, $current);
|
||||
case 'dt':
|
||||
case 'dd':
|
||||
return $this->handleDT($new, $current);
|
||||
case 'rt':
|
||||
case 'rp':
|
||||
return $this->handleRT($new, $current);
|
||||
case 'optgroup':
|
||||
return $this->closeIfCurrentMatches($new, $current, array(
|
||||
'optgroup',
|
||||
));
|
||||
case 'option':
|
||||
return $this->closeIfCurrentMatches($new, $current, array(
|
||||
'option',
|
||||
));
|
||||
case 'tr':
|
||||
return $this->closeIfCurrentMatches($new, $current, array(
|
||||
'tr',
|
||||
));
|
||||
case 'td':
|
||||
case 'th':
|
||||
return $this->closeIfCurrentMatches($new, $current, array(
|
||||
'th',
|
||||
'td',
|
||||
));
|
||||
case 'tbody':
|
||||
case 'thead':
|
||||
case 'tfoot':
|
||||
case 'table': // Spec isn't explicit about this, but it's necessary.
|
||||
|
||||
return $this->closeIfCurrentMatches($new, $current, array(
|
||||
'thead',
|
||||
'tfoot',
|
||||
'tbody',
|
||||
));
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
protected function handleLI($ele, $current)
|
||||
{
|
||||
return $this->closeIfCurrentMatches($ele, $current, array(
|
||||
'li',
|
||||
));
|
||||
}
|
||||
|
||||
protected function handleDT($ele, $current)
|
||||
{
|
||||
return $this->closeIfCurrentMatches($ele, $current, array(
|
||||
'dt',
|
||||
'dd',
|
||||
));
|
||||
}
|
||||
|
||||
protected function handleRT($ele, $current)
|
||||
{
|
||||
return $this->closeIfCurrentMatches($ele, $current, array(
|
||||
'rt',
|
||||
'rp',
|
||||
));
|
||||
}
|
||||
|
||||
protected function closeIfCurrentMatches($ele, $current, $match)
|
||||
{
|
||||
if (in_array($current->tagName, $match, true)) {
|
||||
$current->parentNode->appendChild($ele);
|
||||
} else {
|
||||
$current->appendChild($ele);
|
||||
}
|
||||
|
||||
return $ele;
|
||||
}
|
||||
}
|
||||
183
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/UTF8Utils.php
vendored
Normal file
183
lib/dompdf/vendor/masterminds/html5/src/HTML5/Parser/UTF8Utils.php
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace Masterminds\HTML5\Parser;
|
||||
|
||||
/*
|
||||
Portions based on code from html5lib files with the following copyright:
|
||||
|
||||
Copyright 2009 Geoffrey Sneddon <http://gsnedders.com/>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
use Masterminds\HTML5\Exception;
|
||||
|
||||
class UTF8Utils
|
||||
{
|
||||
/**
|
||||
* The Unicode replacement character.
|
||||
*/
|
||||
const FFFD = "\xEF\xBF\xBD";
|
||||
|
||||
/**
|
||||
* Count the number of characters in a string.
|
||||
* UTF-8 aware. This will try (in order) iconv, MB, libxml, and finally a custom counter.
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function countChars($string)
|
||||
{
|
||||
// Get the length for the string we need.
|
||||
if (function_exists('mb_strlen')) {
|
||||
return mb_strlen($string, 'utf-8');
|
||||
}
|
||||
|
||||
if (function_exists('iconv_strlen')) {
|
||||
return iconv_strlen($string, 'utf-8');
|
||||
}
|
||||
|
||||
if (function_exists('utf8_decode')) {
|
||||
// MPB: Will this work? Won't certain decodes lead to two chars
|
||||
// extrapolated out of 2-byte chars?
|
||||
return strlen(utf8_decode($string));
|
||||
}
|
||||
|
||||
$count = count_chars($string);
|
||||
|
||||
// 0x80 = 0x7F - 0 + 1 (one added to get inclusive range)
|
||||
// 0x33 = 0xF4 - 0x2C + 1 (one added to get inclusive range)
|
||||
return array_sum(array_slice($count, 0, 0x80)) + array_sum(array_slice($count, 0xC2, 0x33));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert data from the given encoding to UTF-8.
|
||||
*
|
||||
* This has not yet been tested with charactersets other than UTF-8.
|
||||
* It should work with ISO-8859-1/-13 and standard Latin Win charsets.
|
||||
*
|
||||
* @param string $data The data to convert
|
||||
* @param string $encoding A valid encoding. Examples: http://www.php.net/manual/en/mbstring.supported-encodings.php
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function convertToUTF8($data, $encoding = 'UTF-8')
|
||||
{
|
||||
/*
|
||||
* From the HTML5 spec: Given an encoding, the bytes in the input stream must be converted
|
||||
* to Unicode characters for the tokeniser, as described by the rules for that encoding,
|
||||
* except that the leading U+FEFF BYTE ORDER MARK character, if any, must not be stripped
|
||||
* by the encoding layer (it is stripped by the rule below). Bytes or sequences of bytes
|
||||
* in the original byte stream that could not be converted to Unicode characters must be
|
||||
* converted to U+FFFD REPLACEMENT CHARACTER code points.
|
||||
*/
|
||||
|
||||
// mb_convert_encoding is chosen over iconv because of a bug. The best
|
||||
// details for the bug are on http://us1.php.net/manual/en/function.iconv.php#108643
|
||||
// which contains links to the actual but reports as well as work around
|
||||
// details.
|
||||
if (function_exists('mb_convert_encoding')) {
|
||||
// mb library has the following behaviors:
|
||||
// - UTF-16 surrogates result in false.
|
||||
// - Overlongs and outside Plane 16 result in empty strings.
|
||||
|
||||
// Before we run mb_convert_encoding we need to tell it what to do with
|
||||
// characters it does not know. This could be different than the parent
|
||||
// application executing this library so we store the value, change it
|
||||
// to our needs, and then change it back when we are done. This feels
|
||||
// a little excessive and it would be great if there was a better way.
|
||||
$save = mb_substitute_character();
|
||||
mb_substitute_character('none');
|
||||
$data = mb_convert_encoding($data, 'UTF-8', $encoding);
|
||||
mb_substitute_character($save);
|
||||
}
|
||||
// @todo Get iconv running in at least some environments if that is possible.
|
||||
elseif (function_exists('iconv') && 'auto' !== $encoding) {
|
||||
// fprintf(STDOUT, "iconv found\n");
|
||||
// iconv has the following behaviors:
|
||||
// - Overlong representations are ignored.
|
||||
// - Beyond Plane 16 is replaced with a lower char.
|
||||
// - Incomplete sequences generate a warning.
|
||||
$data = @iconv($encoding, 'UTF-8//IGNORE', $data);
|
||||
} else {
|
||||
throw new Exception('Not implemented, please install mbstring or iconv');
|
||||
}
|
||||
|
||||
/*
|
||||
* One leading U+FEFF BYTE ORDER MARK character must be ignored if any are present.
|
||||
*/
|
||||
if ("\xEF\xBB\xBF" === substr($data, 0, 3)) {
|
||||
$data = substr($data, 3);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for Unicode code points that are not valid in a document.
|
||||
*
|
||||
* @param string $data A string to analyze
|
||||
*
|
||||
* @return array An array of (string) error messages produced by the scanning
|
||||
*/
|
||||
public static function checkForIllegalCodepoints($data)
|
||||
{
|
||||
// Vestigal error handling.
|
||||
$errors = array();
|
||||
|
||||
/*
|
||||
* All U+0000 null characters in the input must be replaced by U+FFFD REPLACEMENT CHARACTERs.
|
||||
* Any occurrences of such characters is a parse error.
|
||||
*/
|
||||
for ($i = 0, $count = substr_count($data, "\0"); $i < $count; ++$i) {
|
||||
$errors[] = 'null-character';
|
||||
}
|
||||
|
||||
/*
|
||||
* Any occurrences of any characters in the ranges U+0001 to U+0008, U+000B, U+000E to U+001F, U+007F
|
||||
* to U+009F, U+D800 to U+DFFF , U+FDD0 to U+FDEF, and characters U+FFFE, U+FFFF, U+1FFFE, U+1FFFF,
|
||||
* U+2FFFE, U+2FFFF, U+3FFFE, U+3FFFF, U+4FFFE, U+4FFFF, U+5FFFE, U+5FFFF, U+6FFFE, U+6FFFF, U+7FFFE,
|
||||
* U+7FFFF, U+8FFFE, U+8FFFF, U+9FFFE, U+9FFFF, U+AFFFE, U+AFFFF, U+BFFFE, U+BFFFF, U+CFFFE, U+CFFFF,
|
||||
* U+DFFFE, U+DFFFF, U+EFFFE, U+EFFFF, U+FFFFE, U+FFFFF, U+10FFFE, and U+10FFFF are parse errors.
|
||||
* (These are all control characters or permanently undefined Unicode characters.)
|
||||
*/
|
||||
// Check PCRE is loaded.
|
||||
$count = preg_match_all(
|
||||
'/(?:
|
||||
[\x01-\x08\x0B\x0E-\x1F\x7F] # U+0001 to U+0008, U+000B, U+000E to U+001F and U+007F
|
||||
|
|
||||
\xC2[\x80-\x9F] # U+0080 to U+009F
|
||||
|
|
||||
\xED(?:\xA0[\x80-\xFF]|[\xA1-\xBE][\x00-\xFF]|\xBF[\x00-\xBF]) # U+D800 to U+DFFFF
|
||||
|
|
||||
\xEF\xB7[\x90-\xAF] # U+FDD0 to U+FDEF
|
||||
|
|
||||
\xEF\xBF[\xBE\xBF] # U+FFFE and U+FFFF
|
||||
|
|
||||
[\xF0-\xF4][\x8F-\xBF]\xBF[\xBE\xBF] # U+nFFFE and U+nFFFF (1 <= n <= 10_{16})
|
||||
)/x', $data, $matches);
|
||||
for ($i = 0; $i < $count; ++$i) {
|
||||
$errors[] = 'invalid-codepoint';
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user