mirror of
https://github.com/php/phd.git
synced 2026-03-23 22:52:05 +01:00
567 lines
16 KiB
PHP
567 lines
16 KiB
PHP
<?php
|
|
namespace phpdotnet\phd;
|
|
|
|
abstract class Format extends ObjectStorage
|
|
{
|
|
/**
|
|
* Represents a short description.
|
|
* Used in createLink()
|
|
*
|
|
* @var integer
|
|
* @usedby createLink()
|
|
*/
|
|
const SDESC = 1;
|
|
|
|
/**
|
|
* Represents a long description.
|
|
* Used in createLink()
|
|
*
|
|
* @var integer
|
|
* @usedby createLink()
|
|
*/
|
|
const LDESC = 2;
|
|
|
|
protected Config $config;
|
|
|
|
private $elementmap = array();
|
|
private $textmap = array();
|
|
private $formatname = "UNKNOWN";
|
|
protected IndexRepository $indexRepository;
|
|
|
|
protected $title;
|
|
protected $fp = array();
|
|
protected $ext;
|
|
protected $outputdir;
|
|
protected $chunked;
|
|
protected $flags = 0;
|
|
|
|
/* Processing Instructions Handlers */
|
|
private $pihandlers = array();
|
|
|
|
/* Indexing maps */
|
|
protected $indexes = array();
|
|
protected $children = array();
|
|
protected $refs = array();
|
|
protected $vars = array();
|
|
protected $classes = array();
|
|
protected $examples = array();
|
|
|
|
private $autogen = array();
|
|
|
|
private static $highlighters = array();
|
|
|
|
/* See self::parse() */
|
|
protected $appendToBuffer = false;
|
|
protected $buffer = "";
|
|
|
|
/* Table handling */
|
|
protected $TABLE = array();
|
|
|
|
/**
|
|
* Name of the ID currently being processed
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $CURRENT_ID = "";
|
|
|
|
public function __construct(Config $config) {
|
|
$this->config = $config;
|
|
if ($this->config->indexcache()) {
|
|
$this->indexRepository = $this->config->indexcache();
|
|
if (!($this instanceof Index)) {
|
|
$this->sortIDs();
|
|
}
|
|
}
|
|
}
|
|
|
|
abstract public function transformFromMap($open, $tag, $name, $attrs, $props);
|
|
abstract public function UNDEF($open, $name, $attrs, $props);
|
|
abstract public function TEXT($value);
|
|
abstract public function CDATA($value);
|
|
|
|
/**
|
|
* Create link to chunk.
|
|
*
|
|
* @param string $for Chunk ID
|
|
* @param string &$desc Description of link, to be filled if neccessary
|
|
* @param integer $type Format of description, Format::SDESC or
|
|
* Format::LDESC
|
|
*
|
|
* @return string Relative or absolute URI to access $for
|
|
*/
|
|
abstract public function createLink(
|
|
$for, &$desc = null, $type = Format::SDESC
|
|
);
|
|
|
|
abstract public function appendData($data);
|
|
|
|
/**
|
|
* Called by Format::notify()
|
|
*
|
|
* Possible events:
|
|
* - Render::STANDALONE
|
|
* Always called with true as value from Render::attach()
|
|
*
|
|
*
|
|
* - Render::INIT
|
|
* Called from Render::execute() when rendering
|
|
* is being started. Value is always true
|
|
*
|
|
* - Render::FINALIZE (from Render::execute())
|
|
* Called from Render::execute() when there is
|
|
* nothing more to read in the XML file.
|
|
*
|
|
* - Render::VERBOSE
|
|
* Called if the user specified the --verbose option
|
|
* as commandline parameter. Called in render.php
|
|
*
|
|
* - Render::CHUNK
|
|
* Called when a new chunk is opened or closed.
|
|
* Value is either Render::OPEN or Render::CLOSE
|
|
*
|
|
* @param integer $event Event flag (see Render class)
|
|
* @param mixed $val Additional value flag. Depends
|
|
* on $event type
|
|
*
|
|
* @return void
|
|
*/
|
|
abstract public function update($event, $value = null);
|
|
|
|
public final function parsePI($target, $data) {
|
|
if (isset($this->pihandlers[$target])) {
|
|
return $this->pihandlers[$target]->parse($target, $data);
|
|
}
|
|
}
|
|
|
|
public final function registerPIHandlers($pihandlers) {
|
|
foreach ($pihandlers as $target => $classname) {
|
|
$class = __NAMESPACE__ . "\\" . $classname;
|
|
$this->pihandlers[$target] = new $class($this);
|
|
}
|
|
}
|
|
|
|
public function getPIHandler($target) {
|
|
return $this->pihandlers[$target];
|
|
}
|
|
|
|
public function sortIDs() {
|
|
$this->indexes = $this->indexRepository->getIndexes();
|
|
$this->children = $this->indexRepository->getChildren();
|
|
$this->refs = $this->indexRepository->getRefNames();
|
|
$this->vars = $this->indexRepository->getVarNames();
|
|
$this->classes = $this->indexRepository->getClassNames();
|
|
$this->examples = $this->indexRepository->getExamples();
|
|
}
|
|
|
|
public function SQLiteIndex($context, $index, $id, $filename, $parent, $sdesc, $ldesc, $element, $previous, $next, $chunk) {
|
|
$this->indexes[$id] = array(
|
|
"docbook_id" => $id,
|
|
"filename" => $filename,
|
|
"parent_id" => $parent,
|
|
"sdesc" => $sdesc,
|
|
"ldesc" => $ldesc,
|
|
"element" => $element,
|
|
"previous" => $previous,
|
|
"next" => $next,
|
|
"chunk" => $chunk,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Calls update().
|
|
*
|
|
* @param integer $event Event flag. See Render class for constants
|
|
* like Render::INIT and Render::CHUNK
|
|
* @param mixed $val Value; depends on $event flag
|
|
*
|
|
* @return void
|
|
*/
|
|
final public function notify($event, $val = null)
|
|
{
|
|
$this->update($event, $val);
|
|
}
|
|
|
|
public function setTitle($title) {
|
|
$this->title = $title;
|
|
}
|
|
|
|
public function getTitle() {
|
|
return $this->title;
|
|
}
|
|
|
|
/**
|
|
* Set file extension used when chunking and writing
|
|
* out files.
|
|
*
|
|
* @param string $ext File extension without dot
|
|
*
|
|
* @return void
|
|
*
|
|
* @see getExt()
|
|
*/
|
|
public function setExt($ext)
|
|
{
|
|
$this->ext = $ext;
|
|
}
|
|
|
|
/**
|
|
* Returns file extension without
|
|
* leading dot.
|
|
*
|
|
* @return string File extension.
|
|
*
|
|
* @see setExt()
|
|
*/
|
|
public function getExt() {
|
|
return $this->ext;
|
|
}
|
|
|
|
public function setOutputDir($outputdir) {
|
|
$this->outputdir = $outputdir;
|
|
}
|
|
|
|
public function getOutputDir() {
|
|
return $this->outputdir;
|
|
}
|
|
|
|
public function setChunked($chunked) {
|
|
$this->chunked = $chunked;
|
|
}
|
|
|
|
public function isChunked() {
|
|
return $this->chunked;
|
|
}
|
|
|
|
public function setFileStream($stream) {
|
|
$this->fp = $stream;
|
|
}
|
|
|
|
public function getFileStream() {
|
|
return $this->fp;
|
|
}
|
|
|
|
public function pushFileStream($stream) {
|
|
$this->fp[] = $stream;
|
|
}
|
|
|
|
public function popFileStream() {
|
|
return array_pop($this->fp);
|
|
}
|
|
|
|
public function addRefname($id, $ref) {
|
|
$this->refs[$ref] = $id;
|
|
}
|
|
public function addClassname($id, $class) {
|
|
$this->classes[$class] = $id;
|
|
}
|
|
public function addVarname($id, $var) {
|
|
$this->vars[$var] = $id;
|
|
}
|
|
public function getChangelogsForChildrenOf($bookids) {
|
|
return $this->indexRepository->getChangelogsForChildrenOf($bookids);
|
|
}
|
|
public function getChangelogsForMembershipOf($memberships) {
|
|
return $this->indexRepository->getChangelogsForMembershipOf($memberships);
|
|
}
|
|
public function getRefs() {
|
|
return $this->refs;
|
|
}
|
|
public function getExamples() {
|
|
return $this->examples;
|
|
}
|
|
public function getRefnameLink($ref) {
|
|
return isset($this->refs[$ref]) ? $this->refs[$ref] : null;
|
|
}
|
|
public function getClassnameLink($class) {
|
|
return isset($this->classes[$class]) ? $this->classes[$class] : null;
|
|
}
|
|
public function getVarnameLink($var) {
|
|
return isset($this->vars[$var]) ? $this->vars[$var] : null;
|
|
}
|
|
public function getGeneratedExampleID($index) {
|
|
if (array_key_exists($index, $this->examples)) {
|
|
return $this->examples[$index];
|
|
}
|
|
return null;
|
|
}
|
|
final public function registerElementMap(array $map) {
|
|
$this->elementmap = $map;
|
|
}
|
|
final public function registerTextMap(array $map) {
|
|
$this->textmap = $map;
|
|
}
|
|
final public function attach($obj, $inf = array()): void {
|
|
if (!($obj instanceof $this) && get_class($obj) != get_class($this)) {
|
|
throw new \InvalidArgumentException(get_class($this) . " themes *MUST* _inherit_ " .get_class($this). ", got " . get_class($obj));
|
|
}
|
|
$obj->notify(Render::STANDALONE, false);
|
|
parent::attach($obj, $inf);
|
|
}
|
|
final public function getElementMap() {
|
|
return $this->elementmap;
|
|
}
|
|
final public function getTextMap() {
|
|
return $this->textmap;
|
|
}
|
|
final public function registerFormatName($name) {
|
|
$this->formatname = $name;
|
|
}
|
|
public function getFormatName() {
|
|
return $this->formatname;
|
|
}
|
|
|
|
/* Buffer where append data instead of the standard stream (see format's appendData()) */
|
|
final public function parse($xml) {
|
|
$reader = new Reader();
|
|
$render = new Render();
|
|
|
|
$reader->XML("<notatag>" . $xml . "</notatag>");
|
|
|
|
$this->appendToBuffer = true;
|
|
$render->attach($this);
|
|
$render->execute($reader);
|
|
$this->appendToBuffer = false;
|
|
$parsed = $this->buffer;
|
|
$this->buffer = "";
|
|
|
|
return $parsed;
|
|
}
|
|
|
|
final public function autogen($text, $lang = null) {
|
|
if ($lang == NULL) {
|
|
$lang = $this->config->language();
|
|
}
|
|
if (isset($this->autogen[$lang])) {
|
|
if (isset($this->autogen[$lang][$text])) {
|
|
return $this->autogen[$lang][$text];
|
|
}
|
|
if ($lang == $this->config->fallback_language()) {
|
|
throw new \InvalidArgumentException("Cannot autogenerate text for '$text'");
|
|
}
|
|
return $this->autogen($text, $this->config->fallback_language());
|
|
}
|
|
|
|
$filename = $this->config->lang_dir() . $lang . ".ini";
|
|
|
|
if (!file_exists($filename) && strncmp(basename($filename), 'doc-', 4) === 0) {
|
|
$filename = dirname($filename) . DIRECTORY_SEPARATOR . substr(basename($filename), 4);
|
|
}
|
|
|
|
$this->autogen[$lang] = parse_ini_file($filename);
|
|
return $this->autogen($text, $lang);
|
|
}
|
|
|
|
/* {{{ TOC helper functions */
|
|
|
|
/**
|
|
* Returns the filename for the given id, without the file extension
|
|
*
|
|
* @param string $id XML Id
|
|
*
|
|
* @return mixed Stringular filename or false if no filename
|
|
* can be detected.
|
|
*/
|
|
final public function getFilename($id)
|
|
{
|
|
return isset($this->indexes[$id]['filename'])
|
|
? $this->indexes[$id]['filename']
|
|
: false;
|
|
}
|
|
|
|
final public function getPrevious($id) {
|
|
return $this->indexes[$id]["previous"];
|
|
}
|
|
final public function getNext($id) {
|
|
return $this->indexes[$id]["next"];
|
|
}
|
|
final public function getParent($id) {
|
|
return $this->indexes[$id]["parent_id"];
|
|
}
|
|
final public function getLongDescription($id, &$isLDesc = null) {
|
|
if ($this->indexes[$id]["ldesc"]) {
|
|
$isLDesc = true;
|
|
return $this->indexes[$id]["ldesc"];
|
|
} else {
|
|
$isLDesc = false;
|
|
return $this->indexes[$id]["sdesc"];
|
|
}
|
|
}
|
|
final public function getShortDescription($id, &$isSDesc = null) {
|
|
if ($this->indexes[$id]["sdesc"]) {
|
|
$isSDesc = true;
|
|
return $this->indexes[$id]["sdesc"];
|
|
} else {
|
|
$isSDesc = false;
|
|
return $this->indexes[$id]["ldesc"];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns an array of children IDs of given ID.
|
|
*
|
|
* @param string $id XML ID to retrieve children for.
|
|
*
|
|
* @return array Array of XML IDs
|
|
*/
|
|
final public function getChildren($id)
|
|
{
|
|
if (!isset($this->children[$id])
|
|
|| !is_array($this->children[$id])
|
|
|| count($this->children[$id]) == 0
|
|
) {
|
|
return [];
|
|
}
|
|
return $this->children[$id];
|
|
}
|
|
|
|
/**
|
|
* Tells you if the given ID is to be chunked or not.
|
|
*
|
|
* @param string $id XML ID to get chunk status for
|
|
*
|
|
* @return boolean True if it is to be chunked
|
|
*/
|
|
final public function isChunkID($id)
|
|
{
|
|
return isset($this->indexes[$id]['chunk'])
|
|
? $this->indexes[$id]['chunk']
|
|
: false;
|
|
}
|
|
|
|
final public function getRootIndex() {
|
|
static $root = null;
|
|
if ($root == null) {
|
|
$root = $this->indexRepository->getRootIndex();
|
|
}
|
|
return $root;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Table helper functions */
|
|
public function tgroup($attrs) {
|
|
if (isset($attrs["cols"])) {
|
|
$this->TABLE["cols"] = $attrs["cols"];
|
|
unset($attrs["cols"]);
|
|
}
|
|
|
|
$this->TABLE["defaults"] = $attrs;
|
|
$this->TABLE["colspec"] = array();
|
|
}
|
|
public function colspec(array $attrs) {
|
|
$colspec = self::getColSpec($attrs);
|
|
$this->TABLE["colspec"][$colspec["colnum"]] = $colspec;
|
|
return $colspec;
|
|
}
|
|
public function getColspec(array $attrs) {
|
|
/* defaults */
|
|
$defaults["colname"] = count($this->TABLE["colspec"])+1;
|
|
$defaults["colnum"] = count($this->TABLE["colspec"])+1;
|
|
|
|
return array_merge($defaults, $this->TABLE["defaults"], $attrs);
|
|
}
|
|
public function getColCount() {
|
|
return $this->TABLE["cols"];
|
|
}
|
|
public function initRow() {
|
|
$this->TABLE["next_colnum"] = 1;
|
|
}
|
|
public function getEntryOffset(array $attrs) {
|
|
$curr = $this->TABLE["next_colnum"];
|
|
foreach($this->TABLE["colspec"] as $spec) {
|
|
if ($spec["colname"] == $attrs["colname"]) {
|
|
$colnum = $spec["colnum"];
|
|
$this->TABLE["next_colnum"] += $colnum-$curr;
|
|
return $colnum-$curr;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
public function colspan(array $attrs) {
|
|
if (isset($attrs["namest"])) {
|
|
foreach($this->TABLE["colspec"] as $spec) {
|
|
if ($spec["colname"] == $attrs["namest"]) {
|
|
$from = $spec["colnum"];
|
|
continue;
|
|
}
|
|
if ($spec["colname"] == $attrs["nameend"]) {
|
|
$to = $spec["colnum"];
|
|
continue;
|
|
}
|
|
}
|
|
$colspan = $to-$from+1;
|
|
$this->TABLE["next_colnum"] += $colspan;
|
|
return $colspan;
|
|
}
|
|
$this->TABLE["next_colnum"]++;
|
|
return 1;
|
|
}
|
|
public function rowspan($attrs) {
|
|
if (isset($attrs["morerows"])) {
|
|
return $attrs["morerows"]+1;
|
|
}
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/**
|
|
* Highlight (color) the given piece of source code
|
|
*
|
|
* @param string $text Text to highlight
|
|
* @param string $role Source code role to use (php, xml, html, ...)
|
|
* @param string $format Format to highlight (pdf, xhtml, troff, ...)
|
|
*
|
|
* @return string Highlighted code
|
|
*/
|
|
public function highlight($text, $role = 'php', $format = 'xhtml')
|
|
{
|
|
if (!isset(self::$highlighters[$format])) {
|
|
$class = $this->config->highlighter();
|
|
self::$highlighters[$format] = $class::factory($format);
|
|
}
|
|
|
|
return self::$highlighters[$format]->highlight(
|
|
$text, $role, $format
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Provide a nested list of IDs from the document root to the CURRENT_ID.
|
|
*
|
|
* @param string $name The name of the current element.
|
|
* @param mixed[] $props Properties relating to the current element.
|
|
*
|
|
* @return string A nested list of IDs from the root to the CURRENT_ID.
|
|
*/
|
|
public function getDebugTree($name, $props)
|
|
{
|
|
/* Build the list of IDs from the CURRENT_ID to the root. */
|
|
$ids = array();
|
|
$id = $this->CURRENT_ID;
|
|
while($id != '')
|
|
{
|
|
$ids[] = '<' . $this->indexes[$id]['element'] . ' id="' . $id . '">';
|
|
$id = $this->indexes[$id]['parent_id'];
|
|
}
|
|
|
|
/* Reverse the list so that it goes form the root to the CURRENT_ID. */
|
|
$ids = array_reverse($ids);
|
|
|
|
/* Build an indented tree view of the ids. */
|
|
$tree = '';
|
|
$indent = 0;
|
|
array_walk($ids, function($value, $key) use(&$tree, &$indent)
|
|
{
|
|
$tree .= str_repeat(' ', $indent++) . $value . PHP_EOL;
|
|
});
|
|
|
|
/* Add the open and closed sibling and the current element. */
|
|
$tree .=
|
|
str_repeat(' ', $indent) . '<' . $props['sibling'] . '>' . PHP_EOL .
|
|
str_repeat(' ', $indent) . '...' . PHP_EOL .
|
|
str_repeat(' ', $indent) . '</' . $props['sibling'] . '>' . PHP_EOL .
|
|
str_repeat(' ', $indent) . '<' . $name . '>' . PHP_EOL;
|
|
|
|
return $tree;
|
|
}
|
|
}
|