for refactoring * Textile's procedural code into a class framework * * Additions and fixes Copyright (c) 2006 Alex Shiels https://twitter.com/tellyworth * Additions and fixes Copyright (c) 2010 Stef Dawson http://stefdawson.com/ * Additions and fixes Copyright (c) 2010-17 Netcarver https://github.com/netcarver * Additions and fixes Copyright (c) 2011 Jeff Soo http://ipsedixit.net/ * Additions and fixes Copyright (c) 2012 Robert Wetzlmayr http://wetzlmayr.com/ * Additions and fixes Copyright (c) 2012-19 Jukka Svahn http://rahforum.biz/ * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name Textile nor the names of its contributors may be used to * endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* Textile usage examples. Block modifier syntax: Header: h(1-6). Paragraphs beginning with 'hn. ' (where n is 1-6) are wrapped in header tags. Example: h1. Header... ->
Text
Blockquote: bq. Example: bq. Block quotation... ->Block quotation...Blockquote with citation: bq.:http://citation.url Example: bq.:http://example.com/ Text... ->
Text...Footnote: fn(1-100). Example: fn1. Footnote... ->
Footnote...
Numeric list: #, ## Consecutive paragraphs beginning with # are wrapped in ordered list tags. Example:computer code
%(bob)span% -> span
==notextile== -> leave text alone (do not format)
"linktext":url -> linktext
"linktext(title)":url -> linktext
"$":url -> url
"$(title)":url -> url
!imageurl! ->
!imageurl(alt text)! ->
!imageurl!:linkurl ->
ABC(Always Be Closing) -> ABC
Linked Notes:
Allows the generation of an automated list of notes with links.
Linked notes are composed of three parts, a set of named _definitions_, a set of
_references_ to those definitions and one or more _placeholders_ indicating where
the consolidated list of notes is to be placed in your document.
Definitions:
Each note definition must occur in its own paragraph and should look like this...
note#mynotelabel. Your definition text here.
You are free to use whatever label you wish after the # as long as it is made up
of letters, numbers, colon(:) or dash(-).
References:
Each note reference is marked in your text like this[#mynotelabel] and
it will be replaced with a superscript reference that links into the list of
note definitions.
List placeholder(s):
The note list can go anywhere in your document. You have to indicate where
like this:
notelist.
notelist can take attributes (class#id) like this: notelist(class#id).
By default, the note list will show each definition in the order that they
are referenced in the text by the _references_. It will show each definition with
a full list of backlinks to each reference. If you do not want this, you can choose
to override the backlinks like this...
notelist(class#id)!. Produces a list with no backlinks.
notelist(class#id)^. Produces a list with only the first backlink.
Should you wish to have a specific definition display backlinks differently to this
then you can override the backlink method by appending a link override to the
_definition_ you wish to customise.
note#label. Uses the citelist's setting for backlinks.
note#label!. Causes that definition to have no backlinks.
note#label^. Causes that definition to have one backlink (to the first ref.)
note#label*. Causes that definition to have all backlinks.
Any unreferenced notes will be left out of the list unless you explicitly state
you want them by adding a '+'. Like this...
notelist(class#id)!+. Giving a list of all notes without any backlinks.
You can mix and match the list backlink control and unreferenced links controls
but the backlink control (if any) must go first. Like so: notelist^+. , not
like this: notelist+^.
Example...
Scientists say[#lavader] the moon is small.
note#other. An unreferenced note.
note#lavader(myliclass). "Proof":http://example.com of a small moon.
notelist(myclass#myid)+.
Would output (the actual IDs used would be randomised)...
Scientists say1 the moon is small.
The 'a b c' backlink characters can be altered too. For example if you wanted the notes to have numeric backlinks starting from 1: notelist:1. Table syntax: Simple tables: |a|simple|table|row| |And|Another|table|row| |With an||empty|cell| |=. My table caption goes here |_. A|_. table|_. header|_.row| |A|simple|table|row| Note: Table captions *must* be the first line of the table else treated as a center-aligned cell. Tables with attributes: table{border:1px solid black}. My table summary here {background:#ddd;color:red}. |{}| | | | To specify thead / tfoot / tbody groups, add one of these on its own line above the row(s) you wish to wrap (you may specify attributes before the dot): |^. # thead |-. # tbody |~. # tfoot Column groups: |:\3. 100| Becomes:paragraph
p(#fluid). paragraph ->paragraph
(classes and ids can be combined) p(hector#fluid). paragraph ->paragraph
Curly {brackets} insert arbitrary css style p{line-height:18px}. paragraph ->paragraph
h3{color:red}. header 3 ->paragraph
%[fr]phrase% -> phrase Usually Textile block element syntax requires a dot and space before the block begins, but since lists don't, they can be styled just using braces #{color:blue} one ->h1. Headings are disabled too
* * This doesn't prevent unsafe input values. If you wish to parse untrusted * user-given Textile input, also enable the restricted parser mode with * Parser::setRestricted(). * * bc. $parser = new \Netcarver\Textile\Parser(); * echo $parser * ->setRestricted(true) * ->setLite(true) * ->parse('h1. Hello World!'); * * @param bool $lite TRUE to enable * @return Parser This instance * @since 3.6.0 * @see Parser::isLiteModeEnabled() * @see Parser::setRestricted() * @api */ public function setLite($lite) { $this->lite = (bool) $lite; return $this; } /** * Gets the lite mode status. * * bc. $parser = new \Netcarver\Textile\Parser(); * if ($parser->isLiteModeEnabled() === true) { * echo 'Lite mode is enabled.'; * } * * @return bool TRUE if enabled, FALSE otherwise * @since 3.6.0 * @see Parser::setLite() * @api */ public function isLiteModeEnabled() { return (bool) $this->lite; } /** * Disables and enables images. * * If disabled, image tags are not generated. This option is ideal for * minimalist output such as text-only comments. * * bc. $parser = new \Netcarver\Textile\Parser(); * echo $parser * ->setImages(true) * ->parse('!image.png!'); * * Generates: * * bc.!image.png!
* * @param bool $enabled TRUE to enable, FALSE to disable * @return Parser This instance * @since 3.6.0 * @see Parser::isImageTagEnabled() * @api */ public function setImages($enabled) { $this->noimage = !$enabled; return $this; } /** * Whether images are enabled. * * bc. $parser = new \Netcarver\Textile\Parser(); * if ($parser->isImageTagEnabled() === true) { * echo 'Images are enabled.'; * } * * @return bool TRUE if enabled, FALSE otherwise * @since 3.6.0 * @see Parser::setImages() * @api */ public function isImageTagEnabled() { return !$this->noimage; } /** * Sets link relationship status value. * * This method sets the HTML relationship tokens that are applied to links * generated by PHP-Textile. * * bc. $parser = new \Netcarver\Textile\Parser(); * echo $parser * ->setLinkRelationShip('nofollow') * ->parse('"Link":http://example.com/'); * * Generates: * * bc. * * @param string|array $relationship The HTML rel attribute value * @return Parser This instance * @since 3.6.0 * @see Parser::getLinkRelationShip() * @api */ public function setLinkRelationShip($relationship) { $this->rel = (string) implode(' ', (array) $relationship); return $this; } /** * Gets the link relationship status value. * * bc. $parser = new \Netcarver\Textile\Parser(); * echo $parse * ->setLinkRelationShip('nofollow') * ->getLinkRelationShip(); * * The above outputs "nofollow". * * @return string The value * @since 3.6.0 * @see Parser::setLinkRelationShip() * @api */ public function getLinkRelationShip() { return $this->rel; } /** * Enables restricted parser mode. * * This option should be enabled when parsing untrusted user input, * including comments or forum posts. When enabled, the parser escapes any * raw HTML input, ignores unsafe attributes and links only whitelisted URL * schemes. * * For instance the following malicious input: * * bc. $parser = new \Netcarver\Textile\Parser(); * echo $parser * ->setRestricted(true) * ->parse('Innocent _looking_ "link":javacript:window.alert().'); * * Returns safe, sanitized HTML with valid Textile input still parsed: * * bc.Innocent looking “link”:javacript:window.alert().
* * If left disabled, the parser allows users to mix raw HTML and Textile. * Using the parser in non-restricted on untrusted input, like comments * and forum posts, will lead to XSS issues, as users will be able to use * any HTML code, JavaScript links and Textile attributes in their input. * * @param bool $enabled TRUE to enable, FALSE to disable * @return Parser This instance * @since 3.6.0 * @see Parser::isRestrictedModeEnabled() * @api */ public function setRestricted($enabled) { if ($enabled) { $this->url_schemes = $this->restricted_url_schemes; $this->restricted = true; } else { $this->url_schemes = $this->unrestricted_url_schemes; $this->restricted = false; } return $this; } /** * Whether restricted parser mode is enabled. * * bc. $parser = new \Netcarver\Textile\Parser(); * if ($parser->isRestrictedModeEnabled() === true) { * echo 'PHP-Textile is in restricted mode.'; * } * * @return bool TRUE if enabled, FALSE otherwise * @since 3.6.0 * @see Parser::setRestricted() * @api */ public function isRestrictedModeEnabled() { return (bool) $this->restricted; } /** * Enables and disables raw blocks. * * When raw blocks are enabled, any paragraph blocks wrapped in a tag * not matching Parser::$blockContent or Parser::$phrasingContent will not * be parsed, and instead is left as is. * * bc. $parser = new \Netcarver\Textile\Parser(); * echo $parser * ->setRawBlocks(true) * ->parse('Hello world!
* * @param bool $enabled TRUE to enable, FALSE to disable * @return Parser This instance * @since 3.6.0 * @see Parser::isLineWrapEnabled() * @api */ public function setLineWrap($enabled) { $this->lineWrapEnabled = (bool) $enabled; return $this; } /** * Whether line-wrapping is enabled. * * bc. $parser = new \Netcarving\Textile\Parser(); * if ($parser->isLineWrapEnabled() === true) { * echo 'Line-wrapping is enabled.'; * } * * @return bool TRUE if enabled, FALSE otherwise * @see Parser::setLineWrap() * @since 3.6.0 * @api */ public function isLineWrapEnabled() { return (bool) $this->lineWrapEnabled; } /** * Sets a substitution symbol. * * This method lets you to redefine a substitution symbol. The following * sets the 'half' glyph: * * bc. $parser = new \Netcarver\Textile\Parser(); * echo $parser * ->setSymbol('half', '1⁄2') * ->parse('Hello [1/2] World!'); * * Generates: * * bc.Hello 1⁄2 World!
* * Symbol can be set to FALSE to disable it: * * bc. $parser = new \Netcarver\Textile\Parser(); * $parser->setSymbol('dimension', false); * * See Parser::getSymbol() to find out all available symbols. * * @param string $name Name of the symbol to assign a new value to * @param string|bool $value New value for the symbol, or FALSE to disable * @return Parser This instance * @see Parser::getSymbol() * @api */ public function setSymbol($name, $value) { if ($value !== false) { $value = (string) $value; } $this->symbols[(string) $name] = $value; $this->rebuild_glyphs = true; return $this; } /** * Gets a symbol definitions. * * This method gets a symbol definition by name, or the full symbol table * as an array. * * bc. $parser = new \Netcarver\Textile\Parser(); * echo $parser->getSymbol('dimension'); * * To get all available symbol definitions: * * bc. $parser = new \Netcarver\Textile\Parser(); * print_r($parser->getSymbol()); * * @param string|null $name The name of the symbol, or NULL if requesting the symbol table * @return array|string The symbol table or the requested symbol * @throws \InvalidArgumentException * @see Parser::setSymbol() * @api */ public function getSymbol($name = null) { if ($name !== null) { if (isset($this->symbols[$name])) { return $this->symbols[$name]; } throw new \InvalidArgumentException('The specified name does not match any symbols.'); } return $this->symbols; } /** * Sets base relative image prefix. * * The given string is used to prefix relative image paths, usually an * absolute HTTP address pointing a the site's image, or upload, directory. * PHP-Textile to convert relative paths to absolute, or prefixed paths. * * bc. $parser = new \Netcarver\Textile\Parser(); * $parser->setImagePrefix('https://static.example.com/images/'); * * @param string $prefix The prefix * @return Parser This instance * @since 3.7.0 * @see Parser::getImagePrefix() * @api */ public function setImagePrefix($prefix) { $this->relImagePrefix = (string) $prefix; return $this; } /** * Gets base relative image prefix. * * bc. $parser = new \Netcarver\Textile\Parser(); * echo $parser->getImagePrefix(); * * @return string The prefix * @since 3.7.0 * @see Parser::setImagePrefix() * @api */ public function getImagePrefix() { return (string) $this->relImagePrefix; } /** * Sets base relative link prefix. * * The given string is used to prefix relative link paths. This allows * PHP-Textile convert relative paths to absolute, or prefixed, links. * * bc. $parser = new \Netcarver\Textile\Parser(); * $parser->setLinkPrefix('https://example.com/'); * * @param string $prefix The prefix * @return Parser This instance * @since 3.7.0 * @see Parser::getLinkPrefix() * @api */ public function setLinkPrefix($prefix) { $this->relLinkPrefix = (string) $prefix; return $this; } /** * Gets base relative link prefix. * * bc. $parser = new \Netcarver\Textile\Parser(); * echo $parser->getLinkPrefix(); * * @return string The prefix * @since 3.7.0 * @see Parser::setLinkPrefix() * @api */ public function getLinkPrefix() { return (string) $this->relLinkPrefix; } /** * Sets base relative image and link directory path. * * This is used when Textile is supplied with a relative image or link path. * Allows client systems to have PHP-Textile convert relative paths to * absolute or prefixed paths. This method is used to set that base path, * usually an absolute HTTP address pointing to a directory. Note that * despite its name it applies to both links and images. * * bc. $parser = new \Netcarver\Textile\Parser(); * $parser->setRelativeImagePrefix('https://example.com/'); * * @param string $prefix The string to prefix all relative image paths with * @return Parser This instance * @deprecated in 3.7.0 * @see Parser::setImagePrefix * @see Parser::setLinkPrefix * @api */ public function setRelativeImagePrefix($prefix = '') { trigger_error( 'Parser::setRelativeImagePrefix() is deprecated.'. 'Use Parser::setImagePrefix() and Parser::setLinkPrefix() instead.', E_USER_DEPRECATED ); $this->relativeImagePrefix = $prefix; return $this; } /** * Enables dimensionless images. * * If enabled, image width and height attributes will not be included in * rendered image tags. Normally, PHP-Textile will add width and height * to images linked with a local relative path, as long as the image file * can be accessed. * * bc. $parser = new \Netcarver\Textile\Parser(); * echo $parser * ->setDimensionlessImages(true) * ->parse('!image.jpg!'); * * @param bool $dimensionless TRUE to disable image dimensions, FALSE to enable * @return Parser This instance * @see Parser::getDimensionlessImages() * @api */ public function setDimensionlessImages($dimensionless = true) { $this->dimensionless_images = (bool) $dimensionless; return $this; } /** * Whether dimensionless images are enabled. * * bc. $parser = new \Netcarver\Textile\Parser(); * if ($parser->getDimensionlessImages() === true) { * echo 'Images do not get dimensions.'; * } * * @return bool TRUE if images will not get dimensions, FALSE otherwise * @see Parser::setDimensionlessImages() * @api */ public function getDimensionlessImages() { return (bool) $this->dimensionless_images; } /** * Gets PHP-Textile version number. * * bc. $parser = new \Netcarver\Textile\Parser(); * echo $parser->getVersion(); * * @return string Version number * @api */ public function getVersion() { return $this->ver; } /** * Encodes the given text. * * bc. $parser = new \Netcarver\Textile\Parser(); * $parser->textileEncode('Some content to encode.'); * * @param string $text The text to be encoded * @return string The encoded text * @api */ public function textileEncode($text) { return (string)preg_replace('/&(?!(?:[a-z][a-z\d]*|#(?:\d+|x[a-f\d]+));)/i', '&', $text); } /** * Parses the given Textile input according to the previously set options. * * The parser's features can be changed by using the various public setter * methods this class has. The most basic use case is: * * bc. $parser = new \Netcarver\Textile\Parser(); * echo $parser->parse('h1. Hello World!'); * * The above parses trusted input in full-feature mode, generating: * * bc.' . $def . '
'; } if ($this->isLineWrapEnabled()) { $term = str_replace("\n", "