├── .gitignore ├── .travis.yml ├── Builder.php ├── Directive.php ├── Directives ├── CodeBlock.php ├── Document.php ├── Dummy.php ├── Raw.php ├── RedirectionTitle.php ├── Replace.php └── Toctree.php ├── Document.php ├── Environment.php ├── ErrorManager.php ├── HTML ├── Directives │ ├── Div.php │ ├── Figure.php │ ├── Image.php │ ├── Meta.php │ ├── Stylesheet.php │ ├── Title.php │ ├── Url.php │ └── Wrap.php ├── Document.php ├── Environment.php ├── Kernel.php ├── Nodes │ ├── AnchorNode.php │ ├── CodeNode.php │ ├── FigureNode.php │ ├── ImageNode.php │ ├── ListNode.php │ ├── MetaNode.php │ ├── ParagraphNode.php │ ├── QuoteNode.php │ ├── SeparatorNode.php │ ├── TableNode.php │ ├── TitleNode.php │ └── TocNode.php └── Span.php ├── Kernel.php ├── LICENSE ├── LaTeX ├── Directives │ ├── Image.php │ ├── LaTeXMain.php │ ├── Meta.php │ ├── Stylesheet.php │ ├── Title.php │ ├── Url.php │ └── Wrap.php ├── Document.php ├── Environment.php ├── Kernel.php ├── Nodes │ ├── AnchorNode.php │ ├── CodeNode.php │ ├── ImageNode.php │ ├── LaTeXMainNode.php │ ├── ListNode.php │ ├── MetaNode.php │ ├── ParagraphNode.php │ ├── QuoteNode.php │ ├── SeparatorNode.php │ ├── TableNode.php │ ├── TitleNode.php │ └── TocNode.php └── Span.php ├── Metas.php ├── Nodes ├── AnchorNode.php ├── BlockNode.php ├── CodeNode.php ├── DocumentNode.php ├── DummyNode.php ├── FigureNode.php ├── ImageNode.php ├── ListNode.php ├── MetaNode.php ├── Node.php ├── ParagraphNode.php ├── QuoteNode.php ├── RawNode.php ├── SeparatorNode.php ├── TableNode.php ├── TitleNode.php ├── TocNode.php └── WrapperNode.php ├── Parser.php ├── README.md ├── Reference.php ├── References └── Doc.php ├── Span.php ├── SubDirective.php ├── autoload.php ├── composer.json ├── phpunit.xml ├── test ├── document │ ├── demo.php │ ├── document.html │ ├── document.rst │ ├── include.rst │ ├── latex.php │ ├── parse.php │ ├── rst.png │ └── style.css └── tree │ ├── .gitignore │ ├── input │ ├── css │ │ └── style.css │ ├── include.rst │ ├── index.rst │ ├── otherpage.rst │ ├── special.rst │ └── subdir │ │ ├── testinclude.rst │ │ └── testing_sub.rst │ ├── latex.php │ └── parse.php └── tests ├── BuilderTests.php ├── EnvironmentTests.php ├── HTMLTests.php ├── ParserTests.php ├── bootstrap.php ├── builder ├── .gitignore └── input │ ├── another.rst │ ├── file.txt │ ├── index.rst │ ├── introduction.rst │ ├── subdir │ └── index.rst │ └── subdirective.rst ├── files ├── code-block-lastline.rst ├── code.rst ├── comment.rst ├── directive.rst ├── empty.rst ├── include.rst ├── inclusion-newline-include.rst ├── inclusion-newline.rst ├── inclusion-policy.rst ├── inclusion-scope-include.rst ├── inclusion-scope.rst ├── inclusion.rst ├── indented-list.rst ├── list-empty.rst ├── list.rst ├── mixed-titles-1.rst ├── mixed-titles-2.rst ├── multi-comment.rst ├── paragraph.rst ├── paragraphs.rst ├── pretty-table.rst ├── quote.rst ├── replace.rst ├── subdir │ └── test.rst ├── table.rst ├── title.rst ├── title2.rst └── titles.rst └── html ├── anonymous.rst ├── bom.rst ├── code-block.rst ├── code-java.rst ├── code-list.rst ├── code.rst ├── comment-3.rst ├── comments.rst ├── crlf.rst ├── css.rst ├── directive-title.rst ├── div.rst ├── empty-p.rst ├── escape.rst ├── figure.rst ├── image-follow.rst ├── image-inline.rst ├── image.rst ├── include-comments.rst ├── include-external.rst ├── indented-list.rst ├── italic.rst ├── link-span.rst ├── links.rst ├── list-dash.rst ├── list-empty.rst ├── list.rst ├── literal.rst ├── nbsp.rst ├── no-eager-literals.rst ├── ordered.rst ├── ordered2.rst ├── pretty-table-multibyte.rst ├── pretty-table.rst ├── quote-title.rst ├── quote.rst ├── quote2.rst ├── quote3.rst ├── raw.rst ├── reference-directive.rst ├── separator.rst ├── strong.rst ├── table-multibyte.rst ├── table.rst ├── table2.rst ├── titles-auto.rst ├── titles.rst ├── unknown-directive.rst ├── variable-wrap.rst └── wrap.rst /.gitignore: -------------------------------------------------------------------------------- 1 | **.swp 2 | /vendor/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.0 5 | - 7.1 6 | - 7.2 7 | 8 | script: 9 | - composer install 10 | - vendor/bin/phpunit 11 | -------------------------------------------------------------------------------- /Builder.php: -------------------------------------------------------------------------------- 1 | errorManager = new ErrorManager; 58 | 59 | if ($kernel) { 60 | $this->kernel = $kernel; 61 | } else { 62 | $this->kernel = new HTML\Kernel; 63 | } 64 | 65 | $this->kernel->initBuilder($this); 66 | } 67 | 68 | public function getErrorManager() 69 | { 70 | return $this->errorManager; 71 | } 72 | 73 | /** 74 | * Adds an hook which will be called on each document after parsing 75 | */ 76 | public function addHook($function) 77 | { 78 | $this->hooks[] = $function; 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * Adds an hook which will be called on each environment during building 85 | */ 86 | public function addBeforeHook($function) 87 | { 88 | $this->beforeHooks[] = $function; 89 | 90 | return $this; 91 | } 92 | 93 | protected function display($text) 94 | { 95 | if ($this->verbose) { 96 | echo $text."\n"; 97 | } 98 | } 99 | 100 | public function build($directory, $targetDirectory = 'output', $verbose = true) 101 | { 102 | $this->verbose = $verbose; 103 | $this->directory = $directory; 104 | $this->targetDirectory = $targetDirectory; 105 | 106 | // Creating output directory if doesn't exists 107 | if (!is_dir($targetDirectory)) { 108 | mkdir($targetDirectory, 0755, true); 109 | } 110 | 111 | // Try to load metas, if it does not exists, create it 112 | $this->display('* Loading metas'); 113 | $this->metas = new Metas($this->loadMetas()); 114 | 115 | // Scan all the metas and the index 116 | $this->display('* Pre-scanning files'); 117 | $this->scan($this->getIndexName()); 118 | $this->scanMetas(); 119 | 120 | // Parses all the documents 121 | $this->parseAll(); 122 | 123 | // Renders all the documents 124 | $this->render(); 125 | 126 | // Saving the meta 127 | $this->display('* Writing metas'); 128 | $this->saveMetas(); 129 | 130 | // Copy the files 131 | $this->display('* Running the copies'); 132 | $this->doMkdir(); 133 | $this->doCopy(); 134 | } 135 | 136 | /** 137 | * Renders all the pending documents 138 | */ 139 | protected function render() 140 | { 141 | $this->display('* Rendering documents'); 142 | foreach ($this->documents as $file => &$document) { 143 | $this->display(' -> Rendering '.$file.'...'); 144 | $target = $this->getTargetOf($file); 145 | 146 | $directory = dirname($target); 147 | if (!is_dir($directory)) { 148 | mkdir($directory, 0755, true); 149 | } 150 | 151 | file_put_contents($target, $document->renderDocument()); 152 | } 153 | } 154 | 155 | /** 156 | * Adding a file to the parse queue 157 | */ 158 | protected function addToParseQueue($file) 159 | { 160 | $this->states[$file] = self::PARSE; 161 | 162 | if (!isset($this->documents[$file])) { 163 | $this->parseQueue[$file] = $file; 164 | } 165 | } 166 | 167 | /** 168 | * Returns the next file to parse 169 | */ 170 | protected function getFileToParse() 171 | { 172 | if ($this->parseQueue) { 173 | return array_shift($this->parseQueue); 174 | } else { 175 | return null; 176 | } 177 | } 178 | 179 | /** 180 | * Parses all the document that need to be parsed 181 | */ 182 | protected function parseAll() 183 | { 184 | $this->display('* Parsing files'); 185 | while ($file = $this->getFileToParse()) { 186 | $this->display(' -> Parsing '.$file.'...'); 187 | // Process the file 188 | $rst = $this->getRST($file); 189 | $parser = new Parser(null, $this->kernel); 190 | 191 | $environment = $parser->getEnvironment(); 192 | $environment->setMetas($this->metas); 193 | $environment->setCurrentFilename($file); 194 | $environment->setCurrentDirectory($this->directory); 195 | $environment->setTargetDirectory($this->targetDirectory); 196 | $environment->setErrorManager($this->errorManager); 197 | $environment->setUseRelativeUrls($this->relativeUrls); 198 | 199 | foreach ($this->beforeHooks as $hook) { 200 | $hook($parser); 201 | } 202 | 203 | if (!file_exists($rst)) { 204 | $this->errorManager->error('Can\'t parse the file '.$rst); 205 | continue; 206 | } 207 | 208 | $document = $this->documents[$file] = $parser->parseFile($rst); 209 | 210 | // Calling all the post-process hooks 211 | foreach ($this->hooks as $hook) { 212 | $hook($document); 213 | } 214 | 215 | // Calling the kernel document tweaking 216 | $this->kernel->postParse($document); 217 | 218 | $dependencies = $document->getEnvironment()->getDependencies(); 219 | 220 | if ($dependencies) { 221 | $this->display(' -> Scanning dependencies of '.$file.'...'); 222 | // Scan the dependencies for this document 223 | foreach ($dependencies as $dependency) { 224 | $this->scan($dependency); 225 | } 226 | } 227 | 228 | // Append the meta for this document 229 | $this->metas->set( 230 | $file, 231 | $this->getUrl($document), 232 | $document->getTitle(), 233 | $document->getTitles(), 234 | $document->getTocs(), 235 | filectime($rst), 236 | $dependencies 237 | ); 238 | } 239 | } 240 | 241 | /** 242 | * Scans a file, this will check the status of the file and tell if it 243 | * needs to be parsed or not 244 | */ 245 | public function scan($file) 246 | { 247 | // If no decision is already made about this file 248 | if (!isset($this->states[$file])) { 249 | $this->display(' -> Scanning '.$file.'...'); 250 | $this->states[$file] = self::NO_PARSE; 251 | $entry = $this->metas->get($file); 252 | $rst = $this->getRST($file); 253 | 254 | if (!$entry || !file_exists($rst) || $entry['ctime'] < filectime($rst)) { 255 | // File was never seen or changed and thus need to be parsed 256 | $this->addToParseQueue($file); 257 | } else { 258 | // Have a look to the file dependencies to knoww if you need to parse 259 | // it or not 260 | $depends = $entry['depends']; 261 | 262 | if (isset($entry['parent'])) { 263 | $depends[] = $entry['parent']; 264 | } 265 | 266 | foreach ($depends as $dependency) { 267 | $this->scan($dependency); 268 | 269 | // If any dependency needs to be parsed, this file needs also to be 270 | // parsed 271 | if ($this->states[$dependency] == self::PARSE) { 272 | $this->addToParseQueue($file); 273 | } 274 | } 275 | } 276 | } 277 | } 278 | 279 | /** 280 | * Scans all the metas 281 | */ 282 | public function scanMetas() 283 | { 284 | $entries = $this->metas->getAll(); 285 | 286 | foreach ($entries as $file => $infos) { 287 | $this->scan($file); 288 | } 289 | } 290 | 291 | /** 292 | * Get the meta file name 293 | */ 294 | protected function getMetaFile() 295 | { 296 | return $this->getTargetFile('meta.php'); 297 | } 298 | 299 | 300 | /** 301 | * Try to inport the metas from the meta files 302 | */ 303 | protected function loadMetas() 304 | { 305 | $metaFile = $this->getMetaFile(); 306 | 307 | if (file_exists($metaFile)) { 308 | return @include($metaFile); 309 | } 310 | 311 | return null; 312 | } 313 | 314 | /** 315 | * Saving the meta files 316 | */ 317 | protected function saveMetas() 318 | { 319 | $metas = 'metas->getAll(), true).';'; 320 | file_put_contents($this->getMetaFile(), $metas); 321 | } 322 | 323 | /** 324 | * Gets the .rst of a source file 325 | */ 326 | public function getRST($file) 327 | { 328 | return $this->getSourceFile($file . '.rst'); 329 | } 330 | 331 | /** 332 | * Gets the name of a target for a file, for instance /introduction/part1 could 333 | * be resolved into /path/to/introduction/part1.html 334 | */ 335 | public function getTargetOf($file) 336 | { 337 | $meta = $this->metas->get($file); 338 | 339 | return $this->getTargetFile($meta['url']); 340 | } 341 | 342 | /** 343 | * Gets the URL of a target file 344 | */ 345 | public function getUrl($document) 346 | { 347 | $environment = $document->getEnvironment(); 348 | 349 | return $environment->getUrl() . '.' . $this->kernel->getFileExtension(); 350 | } 351 | 352 | /** 353 | * Gets the name of a target file 354 | */ 355 | public function getTargetFile($filename) 356 | { 357 | return $this->targetDirectory . '/' . $filename; 358 | } 359 | 360 | /** 361 | * Gets the name of a source file 362 | */ 363 | public function getSourceFile($filename) 364 | { 365 | return $this->directory . '/' . $filename; 366 | } 367 | 368 | /** 369 | * Is the given path absolute ? 370 | */ 371 | protected function isAbsolute(string $path) 372 | { 373 | return $path[0] === DIRECTORY_SEPARATOR || preg_match('~\A[A-Z]:(?![^/\\\\])~i', $path) > 0; 374 | } 375 | 376 | /** 377 | * Run the copy 378 | */ 379 | public function doCopy() 380 | { 381 | foreach ($this->toCopy as $copy) { 382 | list($source, $destination) = $copy; 383 | if (!$this->isAbsolute($source)) { 384 | $source = $this->getSourceFile($source); 385 | } 386 | $destination = $this->getTargetFile($destination); 387 | 388 | if (is_dir($source) && is_dir($destination)) { 389 | $destination = dirname($destination); 390 | } 391 | 392 | shell_exec('cp -R '.$source.' '.$destination); 393 | } 394 | } 395 | 396 | /** 397 | * Add a file to copy 398 | */ 399 | public function copy($source, $destination = null) 400 | { 401 | if ($destination === null) { 402 | $destination = basename($source); 403 | } 404 | 405 | $this->toCopy[] = array($source, $destination); 406 | 407 | return $this; 408 | } 409 | 410 | /** 411 | * Run the directories creation 412 | */ 413 | public function doMkdir() 414 | { 415 | foreach ($this->toMkdir as $mkdir) { 416 | $dir = $this->getTargetFile($mkdir); 417 | 418 | if (!is_dir($dir)) { 419 | mkdir($dir, 0755, true); 420 | } 421 | } 422 | } 423 | 424 | /** 425 | * Creates a directory in the target 426 | * 427 | * @param $directory the directory name to create 428 | */ 429 | public function mkdir($directory) 430 | { 431 | $this->toMkdir[] = $directory; 432 | 433 | return $this; 434 | } 435 | 436 | public function setIndexName($name) 437 | { 438 | $this->indexName = $name; 439 | 440 | return $this; 441 | } 442 | 443 | public function getIndexName() 444 | { 445 | return $this->indexName; 446 | } 447 | 448 | /** 449 | * Use relative URLs for links 450 | */ 451 | public function setUseRelativeUrls($enable) 452 | { 453 | $this->relativeUrls = $enable; 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /Directive.php: -------------------------------------------------------------------------------- 1 | getDocument(); 40 | 41 | $processNode = $this->processNode($parser, $variable, $data, $options); 42 | 43 | if ($processNode) { 44 | if ($variable) { 45 | $environment = $parser->getEnvironment(); 46 | $environment->setVariable($variable, $processNode); 47 | } else { 48 | $document->addNode($processNode); 49 | } 50 | } 51 | 52 | if ($node) { 53 | $document->addNode($node); 54 | } 55 | } 56 | 57 | /** 58 | * This can be overloaded to write a directive that just create one node for the 59 | * document, which is common 60 | * 61 | * The arguments are the same that process 62 | */ 63 | public function processNode(Parser $parser, $variable, $data, array $options) 64 | { 65 | $this->processAction($parser, $variable, $data, $options); 66 | 67 | return null; 68 | } 69 | 70 | /** 71 | * This can be overloaded to write a directive that just do an action without changing 72 | * the nodes of the document 73 | * 74 | * The arguments are the same that process 75 | */ 76 | public function processAction(Parser $parser, $variabe, $data, array $options) 77 | { 78 | } 79 | 80 | /** 81 | * Called at the end of the parsing to finalize the document (add something or tweak nodes) 82 | */ 83 | public function finalize(Document &$document) 84 | { 85 | } 86 | 87 | /** 88 | * Should the following block be passed as a CodeNode? 89 | */ 90 | public function wantCode() 91 | { 92 | return false; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Directives/CodeBlock.php: -------------------------------------------------------------------------------- 1 | getKernel(); 31 | 32 | if ($node instanceof CodeNode) { 33 | $node->setLanguage(trim($data)); 34 | } 35 | 36 | if ($variable) { 37 | $environment = $parser->getEnvironment(); 38 | $environment->setVariable($variable, $node); 39 | } else { 40 | $document = $parser->getDocument(); 41 | $document->addNode($node); 42 | } 43 | } 44 | } 45 | 46 | public function wantCode() 47 | { 48 | return true; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Directives/Document.php: -------------------------------------------------------------------------------- 1 | $data, 'options' => $options)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Directives/Raw.php: -------------------------------------------------------------------------------- 1 | Undelined! 17 | */ 18 | class Raw extends Directive 19 | { 20 | public function getName() 21 | { 22 | return 'raw'; 23 | } 24 | 25 | public function process(Parser $parser, $node, $variable, $data, array $options) 26 | { 27 | if ($node) { 28 | $kernel = $parser->getKernel(); 29 | 30 | if ($node instanceof CodeNode) { 31 | $node->setRaw(true); 32 | } 33 | 34 | if ($variable) { 35 | $environment = $parser->getEnvironment(); 36 | $environment->setVariable($variable, $node); 37 | } else { 38 | $document = $parser->getDocument(); 39 | $document->addNode($node); 40 | } 41 | } 42 | } 43 | 44 | public function wantCode() 45 | { 46 | return true; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Directives/RedirectionTitle.php: -------------------------------------------------------------------------------- 1 | getDocument(); 24 | 25 | if ($node) { 26 | if ($node instanceof TitleNode) { 27 | $node->setTarget($data); 28 | } 29 | $document->addNode($node); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Directives/Replace.php: -------------------------------------------------------------------------------- 1 | createSpan($data); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Directives/Toctree.php: -------------------------------------------------------------------------------- 1 | getEnvironment(); 18 | $kernel = $parser->getKernel(); 19 | $files = array(); 20 | 21 | foreach (explode("\n", $node->getValue()) as $file) { 22 | $file = trim($file); 23 | if ($file) { 24 | $environment->addDependency($file); 25 | $files[] = $file; 26 | } 27 | } 28 | 29 | $document = $parser->getDocument(); 30 | $document->addNode($kernel->build('Nodes\TocNode', $files, $environment, $options)); 31 | } 32 | 33 | public function wantCode() 34 | { 35 | return true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Document.php: -------------------------------------------------------------------------------- 1 | environment = $environment; 19 | } 20 | 21 | public function getEnvironment() 22 | { 23 | return $this->environment; 24 | } 25 | 26 | public function renderDocument() 27 | { 28 | return $this->render(); 29 | } 30 | 31 | /** 32 | * Getting all nodes of the document that satisfies the given 33 | * function. If the function is null, all the nodes are returned. 34 | */ 35 | public function getNodes($function = null) 36 | { 37 | $nodes = array(); 38 | 39 | if ($function == null) { 40 | return $this->nodes; 41 | } 42 | 43 | foreach ($this->nodes as $node) { 44 | if ($function($node)) { 45 | $nodes[] = $node; 46 | } 47 | } 48 | 49 | return $nodes; 50 | } 51 | 52 | /** 53 | * Gets the main title of the document 54 | */ 55 | public function getTitle() 56 | { 57 | foreach ($this->nodes as $node) { 58 | if ($node instanceof TitleNode && $node->getLevel() == 1) { 59 | return $node->getValue().''; 60 | } 61 | } 62 | 63 | return null; 64 | } 65 | 66 | /** 67 | * Get the table of contents of the document 68 | */ 69 | public function getTocs() 70 | { 71 | $tocs = array(); 72 | 73 | $nodes = $this->getNodes(function($node) { 74 | return $node instanceof TocNode; 75 | }); 76 | 77 | foreach ($nodes as $toc) { 78 | $files = $toc->getFiles(); 79 | 80 | foreach ($files as &$file) { 81 | $file = $this->environment->canonicalUrl($file); 82 | } 83 | 84 | $tocs[] = $files; 85 | } 86 | 87 | return $tocs; 88 | } 89 | 90 | /** 91 | * Gets the titles hierarchy in arrays, for instance : 92 | * 93 | * array( 94 | * array('Main title', array( 95 | * array('Sub title', array()), 96 | * array('Sub title 2', array(), 97 | * array(array('Redirection', 'target'), array(), 98 | * ) 99 | * ) 100 | */ 101 | public function getTitles() 102 | { 103 | $titles = array(); 104 | $levels = array(&$titles); 105 | 106 | foreach ($this->nodes as $node) { 107 | if ($node instanceof TitleNode) { 108 | $level = $node->getLevel(); 109 | $text = (string)$node->getValue(); 110 | $redirection = $node->getTarget(); 111 | $value = $redirection ? array($text, $redirection) : $text; 112 | 113 | if (isset($levels[$level-1])) { 114 | $parent = &$levels[$level-1]; 115 | $element = array($value, array()); 116 | $parent[] = $element; 117 | $levels[$level] = &$parent[count($parent)-1][1]; 118 | } 119 | } 120 | } 121 | 122 | return $titles; 123 | } 124 | 125 | public function addNode($node) 126 | { 127 | if (is_string($node)) { 128 | $node = new RawNode($node); 129 | } 130 | 131 | if (!$node instanceof Node) { 132 | $this->getEnvironment()->getErrorManager('addNode($node): $node should be a string or a Node'); 133 | } 134 | 135 | $this->nodes[] = $node; 136 | } 137 | 138 | public function prependNode(Node $node) 139 | { 140 | array_unshift($this->nodes, $node); 141 | } 142 | 143 | public function addHeaderNode(Node $node) 144 | { 145 | $this->headerNodes[] = $node; 146 | } 147 | 148 | public function __toString() 149 | { 150 | return $this->render(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Environment.php: -------------------------------------------------------------------------------- 1 | errorManager = new ErrorManager; 59 | 60 | $this->reset(); 61 | } 62 | 63 | /** 64 | * Puts the environment in a clean state for a new parse, like title level order. 65 | */ 66 | public function reset() 67 | { 68 | $this->titleLetters = array(); 69 | $this->currentTitleLevel = 0; 70 | $this->levels = array(); 71 | $this->counters = array(); 72 | 73 | for ($level=0; $level<16; $level++) { 74 | $this->levels[$level] = 1; 75 | $this->counters[$level] = 0; 76 | } 77 | } 78 | 79 | public function getErrorManager() 80 | { 81 | return $this->errorManager; 82 | } 83 | 84 | public function setErrorManager(ErrorManager $errorManager) 85 | { 86 | $this->errorManager = $errorManager; 87 | } 88 | 89 | public function setMetas($metas) 90 | { 91 | $this->metas = $metas; 92 | } 93 | 94 | /** 95 | * Get my parent metas 96 | */ 97 | public function getParent() 98 | { 99 | if (!$this->currentFileName || !$this->metas) { 100 | return null; 101 | } 102 | 103 | $meta = $this->metas->get($this->currentFileName); 104 | 105 | if (!$meta || !isset($meta['parent'])) { 106 | return null; 107 | } 108 | 109 | $parent = $this->metas->get($meta['parent']); 110 | 111 | if (!$parent) { 112 | return null; 113 | } 114 | 115 | return $parent; 116 | } 117 | 118 | /** 119 | * Get the docs involving this document 120 | */ 121 | public function getMyToc() 122 | { 123 | $parent = $this->getParent(); 124 | 125 | if ($parent) { 126 | foreach ($parent['tocs'] as $toc) { 127 | if (in_array($this->currentFileName, $toc)) { 128 | $before = array(); 129 | $after = $toc; 130 | 131 | while ($after) { 132 | $file = array_shift($after); 133 | if ($file == $this->currentFileName) { 134 | return array($before, $after); 135 | } 136 | $before[] = $file; 137 | } 138 | } 139 | } 140 | } 141 | 142 | return null; 143 | } 144 | 145 | /** 146 | * Registers a new reference 147 | */ 148 | public function registerReference(Reference $reference) 149 | { 150 | $name = $reference->getName(); 151 | $this->references[$name] = $reference; 152 | } 153 | 154 | /** 155 | * Resolves a reference 156 | */ 157 | public function resolve($section, $data) 158 | { 159 | if (isset($this->references[$section])) { 160 | $reference = $this->references[$section]; 161 | 162 | return $reference->resolve($this, $data); 163 | } 164 | 165 | $this->errorManager->error('Unknown reference section '.$section); 166 | } 167 | 168 | public function found($section, $data) 169 | { 170 | if (isset($this->references[$section])) { 171 | $reference = $this->references[$section]; 172 | 173 | return $reference->found($this, $data); 174 | } 175 | 176 | $this->errorManager->error('Unknown reference section '.$section); 177 | } 178 | 179 | /** 180 | * Sets the giving variable to a value 181 | * 182 | * @param $variable the variable name 183 | * @param $value the variable value 184 | */ 185 | public function setVariable($variable, $value) 186 | { 187 | $this->variables[$variable] = $value; 188 | } 189 | 190 | /** 191 | * Title level 192 | */ 193 | public function createTitle($level) 194 | { 195 | for ($currentLevel=0; $currentLevel<16; $currentLevel++) { 196 | if ($currentLevel > $level) { 197 | $this->levels[$currentLevel] = 1; 198 | $this->counters[$currentLevel] = 0; 199 | } 200 | } 201 | 202 | $this->levels[$level] = 1; 203 | $this->counters[$level]++; 204 | $token = array('title'); 205 | 206 | for ($i=1; $i<=$level; $i++) { 207 | $token[] = $this->counters[$i]; 208 | } 209 | 210 | return implode('.', $token); 211 | } 212 | 213 | /** 214 | * Get a level number 215 | */ 216 | public function getNumber($level) 217 | { 218 | return $this->levels[$level]++; 219 | } 220 | 221 | /** 222 | * Gets the variable value 223 | * 224 | * @param $name the variable name 225 | */ 226 | public function getVariable($variable, $default = null) 227 | { 228 | if (isset($this->variables[$variable])) { 229 | return $this->variables[$variable]; 230 | } 231 | 232 | return $default; 233 | } 234 | 235 | /** 236 | * Set the link url 237 | */ 238 | public function setLink($name, $url) 239 | { 240 | $name = trim(strtolower($name)); 241 | 242 | if ($name == '_') { 243 | $name = array_shift($this->anonymous); 244 | } 245 | 246 | $this->links[$name] = trim($url); 247 | } 248 | 249 | /** 250 | * Resets the anonymous stack 251 | */ 252 | public function resetAnonymousStack() 253 | { 254 | $this->anonymous = array(); 255 | } 256 | 257 | /** 258 | * Set the current anonymous links name 259 | */ 260 | public function pushAnonymous($name) 261 | { 262 | $this->anonymous[] = trim(strtolower($name)); 263 | } 264 | 265 | /** 266 | * Get a link value 267 | */ 268 | public function getLink($name, $relative = true) 269 | { 270 | $name = trim(strtolower($name)); 271 | if (isset($this->links[$name])) { 272 | $link = $this->links[$name]; 273 | 274 | if ($relative) { 275 | return $this->relativeUrl($link); 276 | } 277 | 278 | return $link; 279 | } 280 | 281 | return null; 282 | } 283 | 284 | /** 285 | * Adds a dependency to the document 286 | */ 287 | public function addDependency($dependency) 288 | { 289 | $dependency = $this->canonicalUrl($dependency); 290 | $this->dependencies[] = $dependency; 291 | } 292 | 293 | /** 294 | * Getting all the dependencies for this environment 295 | */ 296 | public function getDependencies() 297 | { 298 | return $this->dependencies; 299 | } 300 | 301 | /** 302 | * Resolves a relative URL using directories, for instance, if the 303 | * current directory is "path/to/something", and you want to get the 304 | * relative URL to "path/to/something/else.html", the result will 305 | * be else.html. Else, "../" will be added to go to the upper directory 306 | */ 307 | public function relativeUrl($url) 308 | { 309 | // If string contains ://, it is considered as absolute 310 | if (preg_match('/:\\/\\//mUsi', $url)) { 311 | return $url; 312 | } 313 | 314 | // If string begins with "/", the "/" is removed to resolve the 315 | // relative path 316 | if (strlen($url) && $url[0] == '/') { 317 | $url = substr($url, 1); 318 | if ($this->samePrefix($url)) { 319 | // If the prefix is the same, simply returns the file name 320 | $relative = basename($url); 321 | } else { 322 | // Else, returns enough ../ to get upper 323 | $relative = ''; 324 | 325 | for ($k=0; $k<$this->getDepth(); $k++) { 326 | $relative .= '../'; 327 | } 328 | 329 | $relative .= $url; 330 | } 331 | } else { 332 | $relative = $url; 333 | } 334 | 335 | return $relative; 336 | } 337 | 338 | /** 339 | * Use relative URLs for links 340 | */ 341 | public function useRelativeUrls() 342 | { 343 | return $this->relativeUrls; 344 | } 345 | 346 | /** 347 | * Use relative URLs for links 348 | */ 349 | public function setUseRelativeUrls($enable) 350 | { 351 | $this->relativeUrls = $enable; 352 | } 353 | 354 | /** 355 | * Get the depth of the current file name (the number of parent 356 | * directories) 357 | */ 358 | public function getDepth() 359 | { 360 | return count(explode('/', $this->currentFileName))-1; 361 | } 362 | 363 | /** 364 | * Returns true if the given url have the same prefix as the 365 | * current document 366 | */ 367 | protected function samePrefix($url) 368 | { 369 | $partsA = explode('/', $url); 370 | $partsB = explode('/', $this->currentFileName); 371 | 372 | $n = count($partsA); 373 | if ($n != count($partsB)) { 374 | return false; 375 | } 376 | 377 | unset($partsA[$n-1]); 378 | unset($partsB[$n-1]); 379 | 380 | return $partsA == $partsB; 381 | } 382 | 383 | /** 384 | * Returns the directory name 385 | */ 386 | public function getDirName() 387 | { 388 | $dirname = dirname($this->currentFileName); 389 | 390 | if ($dirname == '.') { 391 | return ''; 392 | } 393 | 394 | return $dirname; 395 | } 396 | 397 | /** 398 | * Canonicalize a path, a/b/c/../d/e will become 399 | * a/b/d/e 400 | */ 401 | protected function canonicalize($url) 402 | { 403 | $parts = explode('/', $url); 404 | $stack = array(); 405 | 406 | foreach ($parts as $part) { 407 | if ($part == '..') { 408 | array_pop($stack); 409 | } else { 410 | $stack[] = $part; 411 | } 412 | } 413 | 414 | return implode('/', $stack); 415 | } 416 | 417 | /** 418 | * Gets a canonical URL from the given one 419 | */ 420 | public function canonicalUrl($url) 421 | { 422 | if (strlen($url)) { 423 | if ($url[0] == '/') { 424 | // If the URL begins with a "/", the following is the 425 | // canonical URL 426 | return substr($url, 1); 427 | } else { 428 | // Else, the canonical name is under the current dir 429 | if ($this->getDirName()) { 430 | return $this->canonicalize($this->getDirName() . '/' .$url); 431 | } else { 432 | return $this->canonicalize($url); 433 | } 434 | } 435 | } 436 | 437 | return null; 438 | } 439 | 440 | /** 441 | * Sets the current file name 442 | */ 443 | public function setCurrentFileName($filename) 444 | { 445 | $this->currentFileName = $filename; 446 | } 447 | 448 | /** 449 | * Sets the directory of the current parsing 450 | */ 451 | public function setCurrentDirectory($directory) 452 | { 453 | $this->currentDirectory = $directory; 454 | } 455 | 456 | /** 457 | * Returns an absolute path for a relative given URL 458 | */ 459 | public function absoluteRelativePath($url) 460 | { 461 | return $this->currentDirectory . '/' . $this->getDirName() . '/' . $this->relativeUrl($url); 462 | } 463 | 464 | public function setTargetDirectory($directory) 465 | { 466 | $this->targetDirectory = $directory; 467 | } 468 | 469 | public function getTargetDirectory() 470 | { 471 | return $this->targetDirectory; 472 | } 473 | 474 | public function getUrl() 475 | { 476 | if ($this->url) { 477 | return $this->url; 478 | } else { 479 | return $this->currentFileName; 480 | } 481 | } 482 | 483 | public function setUrl($url) 484 | { 485 | if ($this->getDirName()) { 486 | $url = $this->getDirName() . '/' . $url; 487 | } 488 | 489 | $this->url = $url; 490 | } 491 | 492 | public function getMetas() 493 | { 494 | return $this->metas; 495 | } 496 | 497 | public function getLevel($letter) 498 | { 499 | foreach ($this->titleLetters as $level => $titleLetter) { 500 | if ($letter == $titleLetter) { 501 | return $level; 502 | } 503 | } 504 | 505 | $this->currentTitleLevel++; 506 | $this->titleLetters[$this->currentTitleLevel] = $letter; 507 | return $this->currentTitleLevel; 508 | } 509 | 510 | public function getTitleLetters() 511 | { 512 | return $this->titleLetters; 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /ErrorManager.php: -------------------------------------------------------------------------------- 1 | abort = $abort; 12 | } 13 | 14 | public function error($message) 15 | { 16 | if ($this->abort) { 17 | throw new \Exception($message); 18 | } else { 19 | echo '/!\\ '.$message."\n"; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /HTML/Directives/Div.php: -------------------------------------------------------------------------------- 1 | ', ''); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HTML/Directives/Figure.php: -------------------------------------------------------------------------------- 1 | getEnvironment(); 31 | $url = $environment->relativeUrl($data); 32 | 33 | return new FigureNode(new ImageNode($url, $options), $document); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /HTML/Directives/Image.php: -------------------------------------------------------------------------------- 1 | getEnvironment(); 28 | $url = $environment->relativeUrl($data); 29 | 30 | return new ImageNode($url, $options); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /HTML/Directives/Meta.php: -------------------------------------------------------------------------------- 1 | getDocument(); 26 | 27 | foreach ($options as $key => $value) { 28 | $meta = new MetaNode($key, $value); 29 | $document->addHeaderNode($meta); 30 | } 31 | 32 | if ($node) { 33 | $document->addNode($node); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /HTML/Directives/Stylesheet.php: -------------------------------------------------------------------------------- 1 | getDocument(); 25 | 26 | $document->addCss($data); 27 | 28 | if ($node) { 29 | $document->addNode($node); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /HTML/Directives/Title.php: -------------------------------------------------------------------------------- 1 | getDocument(); 25 | 26 | $document->addHeaderNode(new RawNode(''.htmlspecialchars($data).'')); 27 | 28 | if ($node) { 29 | $document->addNode($node); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /HTML/Directives/Url.php: -------------------------------------------------------------------------------- 1 | getEnvironment(); 21 | $environment->setUrl(trim($data)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HTML/Directives/Wrap.php: -------------------------------------------------------------------------------- 1 | class = $class; 20 | $this->uniqid = $uniqid; 21 | } 22 | 23 | public function getName() 24 | { 25 | return $this->class; 26 | } 27 | 28 | public function processSub(Parser $parser, $document, $variable, $data, array $options) 29 | { 30 | $class = $this->class; 31 | if ($this->uniqid) { 32 | $id = ' id="' . uniqid($this->class) . '"'; 33 | } else { 34 | $id = ''; 35 | } 36 | if ($data) { 37 | $extra = ' data-params="' . htmlspecialchars($data) . '"'; 38 | } else { 39 | $extra = ''; 40 | } 41 | return new WrapperNode($document, '
', '
'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /HTML/Document.php: -------------------------------------------------------------------------------- 1 | nodes as $node) { 15 | $document .= $node->render() . "\n"; 16 | } 17 | 18 | return $document; 19 | } 20 | 21 | public function renderDocument() 22 | { 23 | $document = "\n"; 24 | $document .= "\n"; 25 | 26 | $document .= "\n"; 27 | $document .= "\n"; 28 | foreach ($this->headerNodes as $node) { 29 | $document .= $node->render()."\n"; 30 | } 31 | $document .= "\n"; 32 | 33 | $document .= "\n"; 34 | $document .= $this->render(); 35 | $document .= "\n"; 36 | $document .= "\n"; 37 | 38 | return $document; 39 | } 40 | 41 | public function addCss($css) 42 | { 43 | $environment = $this->getEnvironment(); 44 | $css = $environment->relativeUrl($css); 45 | 46 | $this->addHeaderNode(new RawNode('')); 47 | } 48 | 49 | public function addJs($js) 50 | { 51 | $environment = $this->getEnvironment(); 52 | $js = $environment->relativeUrl($js); 53 | 54 | $this->addHeaderNode(new RawNode('')); 55 | } 56 | 57 | public function addFavicon($url = '/favicon.ico') 58 | { 59 | $environment = $this->getEnvironment(); 60 | $url = $environment->relativeUrl($url); 61 | 62 | $this->addHeaderNode(new RawNode('')); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /HTML/Environment.php: -------------------------------------------------------------------------------- 1 | value.'">'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /HTML/Nodes/CodeNode.php: -------------------------------------------------------------------------------- 1 | raw) { 12 | return $this->value; 13 | } else { 14 | return "
language."\">".htmlspecialchars($this->value)."
"; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /HTML/Nodes/FigureNode.php: -------------------------------------------------------------------------------- 1 | '; 12 | $html .= $this->image->render(); 13 | if ($this->document) { 14 | $caption = trim($this->document->render()); 15 | if ($caption) { 16 | $html .= '
'.$caption.'
'; 17 | } 18 | } 19 | $html .= ''; 20 | 21 | return $html; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HTML/Nodes/ImageNode.php: -------------------------------------------------------------------------------- 1 | options as $key => $value) { 13 | $attributes .= ' '.$key . '="'.htmlspecialchars($value).'"'; 14 | } 15 | 16 | return ''; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /HTML/Nodes/ListNode.php: -------------------------------------------------------------------------------- 1 | ' . $text . ''; 17 | } 18 | 19 | protected function createList($ordered) 20 | { 21 | $keyword = $ordered ? 'ol' : 'ul'; 22 | 23 | return array('<'.$keyword.'>', ''); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /HTML/Nodes/MetaNode.php: -------------------------------------------------------------------------------- 1 | key).'" content="'.htmlspecialchars($this->value).'" />'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /HTML/Nodes/ParagraphNode.php: -------------------------------------------------------------------------------- 1 | value; 12 | 13 | if (trim($text) !== '') { 14 | return '

'.$text.'

'; 15 | } else { 16 | return ''; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /HTML/Nodes/QuoteNode.php: -------------------------------------------------------------------------------- 1 | ".$this->value.""; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /HTML/Nodes/SeparatorNode.php: -------------------------------------------------------------------------------- 1 | '; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /HTML/Nodes/TableNode.php: -------------------------------------------------------------------------------- 1 | '; 12 | foreach ($this->data as $k=>&$row) { 13 | if (!$row) { 14 | continue; 15 | } 16 | 17 | $html .= ''; 18 | foreach ($row as &$col) { 19 | $html .= isset($this->headers[$k]) ? '' : ''; 20 | $html .= $col->render(); 21 | $html .= isset($this->headers[$k]) ? '' : ''; 22 | } 23 | $html .= ''; 24 | } 25 | $html .= ''; 26 | 27 | return $html; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /HTML/Nodes/TitleNode.php: -------------------------------------------------------------------------------- 1 | token.'">level.'>'.$this->value.'level.">"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /HTML/Nodes/TocNode.php: -------------------------------------------------------------------------------- 1 | $this->depth) { 12 | return false; 13 | } 14 | 15 | $html = ''; 16 | foreach ($titles as $k => $entry) { 17 | $path[$level-1] = $k+1; 18 | list($title, $childs) = $entry; 19 | $token = 'title.'.implode('.', $path); 20 | $target = $url.'#'.$token; 21 | 22 | if (is_array($title)) { 23 | list($title, $target) = $title; 24 | $info = $this->environment->resolve('doc', $target); 25 | $target = $this->environment->relativeUrl($info['url']); 26 | } 27 | 28 | $html .= '
  • '.$title.'
  • '; 29 | 30 | if ($childs) { 31 | $html .= ''; 34 | } 35 | } 36 | 37 | return $html; 38 | } 39 | 40 | public function render() 41 | { 42 | if (isset($this->options['hidden'])) { 43 | return ''; 44 | } 45 | 46 | $this->depth = isset($this->options['depth']) ? $this->options['depth'] : 2; 47 | 48 | $html = '
    '; 55 | 56 | return $html; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /HTML/Span.php: -------------------------------------------------------------------------------- 1 | '.$text.''; 12 | } 13 | 14 | public function strongEmphasis($text) 15 | { 16 | return ''.$text.''; 17 | } 18 | 19 | public function nbsp() 20 | { 21 | return ' '; 22 | } 23 | 24 | public function br() 25 | { 26 | return '
    '; 27 | } 28 | 29 | public function literal($text) 30 | { 31 | return ''.$text.''; 32 | } 33 | 34 | public function link($url, $title) 35 | { 36 | return ''.$title.''; 37 | } 38 | 39 | public function escape($span) 40 | { 41 | return htmlspecialchars($span); 42 | } 43 | 44 | public function reference($reference, $value) 45 | { 46 | if ($reference) { 47 | $text = $value['text'] ?: (isset($reference['title']) ? $reference['title'] : ''); 48 | $url = $reference['url']; 49 | if ($value['anchor']) { 50 | $url .= '#' . $value['anchor']; 51 | } 52 | $link = $this->link($url, trim($text)); 53 | } else { 54 | $link = $this->link('#', '(unresolved reference)'); 55 | } 56 | 57 | return $link; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Kernel.php: -------------------------------------------------------------------------------- 1 | getName().'\\'.$name; 21 | } 22 | 23 | /** 24 | * Create an instance of some class 25 | */ 26 | public function build($name, $arg1 = null, $arg2 = null, $arg3 = null, $arg4 = null) 27 | { 28 | $class = $this->getClass($name); 29 | 30 | if ($class) { 31 | return new $class($arg1, $arg2, $arg3, $arg4); 32 | } 33 | 34 | return null; 35 | } 36 | 37 | /** 38 | * Gets the available directives 39 | */ 40 | public function getDirectives() 41 | { 42 | return array( 43 | new Directives\Dummy, 44 | new Directives\CodeBlock, 45 | new Directives\Raw, 46 | new Directives\Replace, 47 | new Directives\Toctree, 48 | new Directives\Document, 49 | new Directives\RedirectionTitle, 50 | ); 51 | } 52 | 53 | /** 54 | * Document references 55 | */ 56 | public function getReferences() 57 | { 58 | return array( 59 | new References\Doc, 60 | new References\Doc('ref'), 61 | ); 62 | } 63 | 64 | /** 65 | * Allowing the kernel to tweak document after the build 66 | */ 67 | public function postParse(Document $document) 68 | { 69 | } 70 | 71 | /** 72 | * Allowing the kernel to tweak the builder 73 | */ 74 | public function initBuilder(Builder $builder) 75 | { 76 | } 77 | 78 | /** 79 | * Get the output files extension 80 | */ 81 | public function getFileExtension() 82 | { 83 | return 'txt'; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) <2013> Grégoire Passault 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /LaTeX/Directives/Image.php: -------------------------------------------------------------------------------- 1 | getEnvironment(); 28 | $url = $environment->relativeUrl($data); 29 | 30 | return new ImageNode($url, $options); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LaTeX/Directives/LaTeXMain.php: -------------------------------------------------------------------------------- 1 | getDocument(); 26 | 27 | foreach ($options as $key => $value) { 28 | $meta = new MetaNode($key, $value); 29 | $document->addHeaderNode($meta); 30 | } 31 | 32 | if ($node) { 33 | $document->addNode($node); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LaTeX/Directives/Stylesheet.php: -------------------------------------------------------------------------------- 1 | getDocument(); 25 | 26 | $document->addHeaderNode(new RawNode('\title{'.$data.'}')); 27 | 28 | if ($node) { 29 | $document->addNode($node); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LaTeX/Directives/Url.php: -------------------------------------------------------------------------------- 1 | getEnvironment(); 21 | $environment->setUrl(trim($data)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LaTeX/Directives/Wrap.php: -------------------------------------------------------------------------------- 1 | class = $class; 19 | } 20 | 21 | public function getName() 22 | { 23 | return $this->class; 24 | } 25 | 26 | public function processSub(Parser $parser, $document, $variable, $data, array $options) 27 | { 28 | return $document; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LaTeX/Document.php: -------------------------------------------------------------------------------- 1 | nodes as $node) { 16 | $document .= $node->render() . "\n"; 17 | } 18 | 19 | return $document; 20 | } 21 | 22 | public function renderDocument() 23 | { 24 | $isMain = count($this->getNodes(function($node) { 25 | return $node instanceof LaTeXMainNode; 26 | })) != 0; 27 | 28 | $document = ''; 29 | 30 | if ($isMain) { 31 | $document .= "\\documentclass[11pt]{report}\n"; 32 | $document .= "\\usepackage[utf8]{inputenc}\n"; 33 | $document .= "\\usepackage[T1]{fontenc}\n"; 34 | $document .= "\\usepackage[french]{babel}\n"; 35 | $document .= "\\usepackage{cite}\n"; 36 | $document .= "\\usepackage{amssymb}\n"; 37 | $document .= "\\usepackage{amsmath}\n"; 38 | $document .= "\\usepackage{mathrsfs}\n"; 39 | $document .= "\\usepackage{graphicx}\n"; 40 | $document .= "\\usepackage{hyperref}\n"; 41 | $document .= "\\usepackage{listings}\n"; 42 | 43 | foreach ($this->headerNodes as $node) { 44 | $document .= $node->render()."\n"; 45 | } 46 | $document .= "\\begin{document}\n"; 47 | } 48 | 49 | $document .= "\label{".$this->environment->getUrl()."}\n"; 50 | $document .= $this->render(); 51 | 52 | if ($isMain) { 53 | $document .= "\\end{document}\n"; 54 | } 55 | 56 | return $document; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /LaTeX/Environment.php: -------------------------------------------------------------------------------- 1 | value.'}'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LaTeX/Nodes/CodeNode.php: -------------------------------------------------------------------------------- 1 | language."}\n"; 12 | $tex .= "\\begin{lstlisting}\n"; 13 | $tex .= $this->value . "\n"; 14 | $tex .= "\\end{lstlisting}\n"; 15 | 16 | return $tex; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LaTeX/Nodes/ImageNode.php: -------------------------------------------------------------------------------- 1 | options as $key => $value) { 13 | $attributes[] = $key . '='.$value; 14 | } 15 | 16 | return '\includegraphics{'.$this->url.'}'; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LaTeX/Nodes/LaTeXMainNode.php: -------------------------------------------------------------------------------- 1 | value; 12 | 13 | if (trim($text) !== '') { 14 | return $text."\n"; 15 | } else { 16 | return ''; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LaTeX/Nodes/QuoteNode.php: -------------------------------------------------------------------------------- 1 | value."\n\\end{quotation}\n"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LaTeX/Nodes/SeparatorNode.php: -------------------------------------------------------------------------------- 1 | data as &$row) { 15 | if (!$row) { 16 | continue; 17 | } 18 | 19 | $rowTex = ''; 20 | $cols = max($cols, count($row)); 21 | 22 | foreach ($row as $n => &$col) { 23 | $rowTex .= $col->render(); 24 | 25 | if ($n+1 < count($row)) { 26 | $rowTex .= ' & '; 27 | } 28 | } 29 | $rowTex .= ' \\\\' . "\n"; 30 | $rows[] = $rowTex; 31 | } 32 | $aligns = array(); 33 | for ($i=0; $i<$cols; $i++) { 34 | $aligns[] = 'l'; 35 | } 36 | 37 | $aligns = '|'.implode('|', $aligns).'|'; 38 | $rows = "\\hline\n".implode("\\hline\n", $rows)."\\hline\n"; 39 | 40 | $tex = "\\begin{tabular}{".$aligns."}\n".$rows."\n\\end{tabular}\n"; 41 | 42 | return $tex; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LaTeX/Nodes/TitleNode.php: -------------------------------------------------------------------------------- 1 | level > 1) { 14 | $type = 'section'; 15 | 16 | for ($i=2; $i<$this->level; $i++) { 17 | $type = 'sub'.$type; 18 | } 19 | } 20 | 21 | return '\\'.$type.'{'.$this->value.'}'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LaTeX/Nodes/TocNode.php: -------------------------------------------------------------------------------- 1 | files as $file) { 14 | $reference = $this->environment->resolve('doc', $file); 15 | $reference['url'] = $this->environment->relativeUrl($reference['url']); 16 | $tex .= "\\input{".$reference['url']."}\n"; 17 | } 18 | 19 | return $tex; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LaTeX/Span.php: -------------------------------------------------------------------------------- 1 | environment->getUrl(); 39 | } 40 | $url = substr($url, 1); 41 | $url = $url ? '#'.$url : ''; 42 | return '\ref{'.$refDoc.$url.'}'; 43 | } else { 44 | return '\href{'.$url.'}{'.$title.'}'; 45 | } 46 | } 47 | 48 | public function escape($span) 49 | { 50 | return $span; 51 | } 52 | 53 | public function reference($reference, $value) 54 | { 55 | if ($reference) { 56 | $file = $reference['file']; 57 | $text = $value['text'] ?: (isset($reference['title']) ? $reference['title'] : ''); 58 | $refDoc = $file; 59 | $url = '#'; 60 | if ($value['anchor']) { 61 | $url .= $value['anchor']; 62 | } 63 | $link = $this->link($url, trim($text), $refDoc); 64 | } else { 65 | $link = $this->link('#', '(unresolved reference)'); 66 | } 67 | 68 | return $link; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Metas.php: -------------------------------------------------------------------------------- 1 | entries = $entries; 14 | } 15 | } 16 | 17 | public function getAll() 18 | { 19 | return $this->entries; 20 | } 21 | 22 | /** 23 | * Sets the meta for url, giving the title, the modification time and 24 | * the dependencies list 25 | */ 26 | public function set($file, $url, $title, $titles, $tocs, $ctime, array $depends) 27 | { 28 | foreach ($tocs as $toc) { 29 | foreach ($toc as $child) { 30 | $this->parents[$child] = $file; 31 | if (isset($this->entries[$child])) { 32 | $this->entries[$child]['parent'] = $file; 33 | } 34 | } 35 | } 36 | 37 | $this->entries[$file] = array( 38 | 'file' => $file, 39 | 'url' => $url, 40 | 'title' => $title, 41 | 'titles' => $titles, 42 | 'tocs' => $tocs, 43 | 'ctime' => $ctime, 44 | 'depends' => $depends 45 | ); 46 | 47 | if (isset($this->parents[$file])) { 48 | $this->entries[$file]['parent'] = $this->parents[$file]; 49 | } 50 | } 51 | 52 | /** 53 | * Gets the meta for a given document reference url 54 | */ 55 | public function get($url) 56 | { 57 | if (isset($this->entries[$url])) { 58 | return $this->entries[$url]; 59 | } else { 60 | return null; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Nodes/AnchorNode.php: -------------------------------------------------------------------------------- 1 | value = implode("\n", $lines); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Nodes/CodeNode.php: -------------------------------------------------------------------------------- 1 | language = $language; 13 | } 14 | 15 | public function getLanguage() 16 | { 17 | return $this->language; 18 | } 19 | 20 | public function setRaw($raw) 21 | { 22 | $this->raw = $raw; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Nodes/DocumentNode.php: -------------------------------------------------------------------------------- 1 | data = $data; 12 | } 13 | 14 | public function render() 15 | { 16 | return ''; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Nodes/FigureNode.php: -------------------------------------------------------------------------------- 1 | image = $image; 13 | $this->document = $document; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Nodes/ImageNode.php: -------------------------------------------------------------------------------- 1 | url = $url; 13 | $this->options = $options; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Nodes/ListNode.php: -------------------------------------------------------------------------------- 1 | lines[] = $line; 20 | } 21 | 22 | public function render() 23 | { 24 | $depth = -1; 25 | $value = ''; 26 | $stack = array(); 27 | 28 | foreach ($this->lines as $line) { 29 | $prefix = $line['prefix']; 30 | $text = $line['text']; 31 | $ordered = $line['ordered']; 32 | $newDepth = $line['depth']; 33 | 34 | if ($depth < $newDepth) { 35 | $tags = $this->createList($ordered); 36 | $value .= $tags[0]; 37 | $stack[] = array($newDepth, $tags[1]."\n"); 38 | $depth = $newDepth; 39 | } 40 | 41 | while ($depth > $newDepth) { 42 | $top = $stack[count($stack)-1]; 43 | 44 | if ($top[0] > $newDepth) { 45 | $value .= $top[1]; 46 | array_pop($stack); 47 | $top = $stack[count($stack)-1]; 48 | $depth = $top[0]; 49 | } 50 | } 51 | 52 | $value .= $this->createElement($text, $prefix)."\n"; 53 | } 54 | 55 | while ($stack) { 56 | list($d, $closing) = array_pop($stack); 57 | $value .= $closing; 58 | } 59 | 60 | return $value; 61 | } 62 | 63 | abstract protected function createElement($text, $prefix); 64 | abstract protected function createList($ordered); 65 | } 66 | -------------------------------------------------------------------------------- /Nodes/MetaNode.php: -------------------------------------------------------------------------------- 1 | key = $key; 13 | $this->value = $value; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Nodes/Node.php: -------------------------------------------------------------------------------- 1 | value = $value; 12 | } 13 | 14 | public function getValue() 15 | { 16 | return $this->value; 17 | } 18 | 19 | public function setValue($value) 20 | { 21 | $this->value = $value; 22 | } 23 | 24 | abstract public function render(); 25 | 26 | public function __toString() 27 | { 28 | return $this->render(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Nodes/ParagraphNode.php: -------------------------------------------------------------------------------- 1 | value; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Nodes/SeparatorNode.php: -------------------------------------------------------------------------------- 1 | level = $level; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Nodes/TableNode.php: -------------------------------------------------------------------------------- 1 | parts = $parts; 16 | $this->data[] = array(); 17 | } 18 | 19 | /** 20 | * Gets the columns count of the table 21 | */ 22 | public function getCols() 23 | { 24 | return count($this->parts[2]); 25 | } 26 | 27 | /** 28 | * Gets the rows count of the table 29 | */ 30 | public function getRows() 31 | { 32 | return count($this->data)-1; 33 | } 34 | 35 | public function push($parts, $line) 36 | { 37 | if ($parts) { 38 | // New line in the tab 39 | if ($parts[2] != $this->parts[2]) { 40 | return false; 41 | } 42 | 43 | if ($parts[0]) { 44 | $this->headers[count($this->data)-1] = true; 45 | } 46 | $this->data[] = array(); 47 | } else { 48 | // Pushing data in the cells 49 | list($header, $pretty, $parts) = $this->parts; 50 | $row = &$this->data[count($this->data)-1]; 51 | 52 | $partsCount = count($parts); 53 | for ($k = 1; $k <= $partsCount; $k++) { 54 | if ($k === $partsCount) { 55 | $data = mb_substr($line, $parts[$k-1]); 56 | } else { 57 | $data = mb_substr($line, $parts[$k-1], $parts[$k]-$parts[$k-1]); 58 | } 59 | 60 | if ($pretty) { 61 | $data = mb_substr($data, 0, -1); 62 | } 63 | 64 | $data = trim($data); 65 | 66 | if (isset($row[$k-1])) { 67 | $row[$k-1] = trim($row[$k-1].' '.$data); 68 | } else { 69 | $row[$k-1] = $data; 70 | } 71 | } 72 | } 73 | 74 | return true; 75 | } 76 | 77 | public function finalize(Parser $parser) 78 | { 79 | foreach ($this->data as &$row) { 80 | if ($row) { 81 | foreach ($row as &$col) { 82 | $col = $parser->createSpan($col); 83 | } 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Nodes/TitleNode.php: -------------------------------------------------------------------------------- 1 | level = $level; 15 | $this->token = $token; 16 | } 17 | 18 | public function getLevel() 19 | { 20 | return $this->level; 21 | } 22 | 23 | public function setTarget($target) 24 | { 25 | $this->target = $target; 26 | } 27 | 28 | public function getTarget() 29 | { 30 | return $this->target; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Nodes/TocNode.php: -------------------------------------------------------------------------------- 1 | files = $files; 16 | $this->environment = $environment; 17 | $this->options = $options; 18 | } 19 | 20 | public function getFiles() 21 | { 22 | return $this->files; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Nodes/WrapperNode.php: -------------------------------------------------------------------------------- 1 | node = $node; 14 | $this->before = $before; 15 | $this->after = $after; 16 | } 17 | 18 | public function render() 19 | { 20 | $contents = $this->node ? $this->node->render() : ''; 21 | 22 | return $this->before . $contents . $this->after; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RST 2 | 3 | [![Build status](https://travis-ci.org/Gregwar/RST.svg?branch=master)](https://travis-ci.org/Gregwar/RST) 4 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YUXRLWHQSWS6L) 5 | 6 | PHP library to parse reStructuredText document 7 | 8 | ## Usage 9 | 10 | The parser can be used this way: 11 | 12 | ```php 13 | `_ 29 | '; 30 | 31 | // Parse it 32 | $document = $parser->parse($rst); 33 | 34 | // Render it 35 | echo $document; 36 | /* Will output, in HTML mode: 37 |

    Hello world

    38 |

    What is it?

    39 |

    This is a RST document!

    40 |

    Where can I get it?

    41 |

    You can get it on the GitHub page

    42 | */ 43 | ``` 44 | 45 | For more information, you can have a look at `test/document/document.rst` and its result 46 | `test/document/document.html` 47 | 48 | ## Using the builder 49 | 50 | The builder is another tool that will parses a whole tree of documents and generates 51 | an output directory containing files. 52 | 53 | You can simply use it with: 54 | 55 | ```php 56 | build('input', 'output'); 60 | ``` 61 | 62 | It will parses all the files in the `input` directory, starting with `index.rst` and 63 | scanning for dependencies references and generates you target files in the `output` 64 | directory. Default format is HTML. 65 | 66 | You can use those methods on it to customize the build: 67 | 68 | * `copy($source, $destination)`: copy the `$source` file or directory to the `$destination` 69 | file or directory of the build 70 | * `mkdir($directory)`: create the `$directory` in build directory 71 | * `addHook($function)`: adds an hook that will be called after each document is parsed, this 72 | hook will be called with the `$document` as parameter and can then tweak it as you want 73 | * `addBeforeHook($function)`: adds an hook that will be called before parsing the 74 | document, the parser will be passed as a parameter 75 | 76 | ## Abort on error 77 | 78 | In some situation you want the build to continue even if there is some errors, 79 | like missing references: 80 | 81 | ```php 82 | getEnvironment()->getErrorManager()->abortOnError(false); 86 | 87 | // Using builder 88 | $builder->getErrorManager()->abortOnError(false); 89 | ``` 90 | 91 | ## Writing directives 92 | 93 | ### Step 1: Extends the Directive class 94 | 95 | Write your own class that extends the `Gregwar\RST\Directive` class, and define the 96 | method `getName()` that return the directive name. 97 | 98 | You can then redefine one of the following method: 99 | 100 | * `processAction()` if your directive simply tweak the document without modifying the nodes 101 | * `processNode()` if your directive is adding a node 102 | * `process()` if your directive is tweaking the node that just follows it 103 | 104 | See `Directive.php` for more information 105 | 106 | ### Step 2: Register your directive 107 | 108 | You can register your directive by directly calling `registerDirective()` on your 109 | `Parser` object. 110 | 111 | Else, you will have to also create your own kernel by extending the `Kernel` class 112 | and adding your own logic to define extra directives, see `Kernel.php` for more information. 113 | Then, pass the kernel when constructing the `Parser` or the `Builder` 114 | 115 | ## License 116 | 117 | This library is under MIT license 118 | -------------------------------------------------------------------------------- /Reference.php: -------------------------------------------------------------------------------- 1 | name = $name; 15 | } 16 | 17 | public function getName() 18 | { 19 | return $this->name; 20 | } 21 | 22 | public function resolve(Environment $environment, $data) 23 | { 24 | $metas = $environment->getMetas(); 25 | $file = $environment->canonicalUrl($data); 26 | 27 | if ($metas && $metas->get($file)) { 28 | $entry = $metas->get($file); 29 | $entry['url'] = $environment->relativeUrl('/'.$entry['url']); 30 | } else { 31 | $entry = array( 32 | 'title' => '(unresolved)', 33 | 'url' => '#' 34 | ); 35 | } 36 | 37 | return $entry; 38 | } 39 | 40 | public function found(Environment $environment, $data) 41 | { 42 | $environment->addDependency($data); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Span.php: -------------------------------------------------------------------------------- 1 | tokenPrefix) { 19 | $this->tokenPrefix = mt_rand().'|'.time(); 20 | } 21 | $this->tokenId++; 22 | return sha1($this->tokenPrefix.'|'.$this->tokenId); 23 | } 24 | 25 | public function __construct(Parser $parser, $span, $tokens = array()) 26 | { 27 | if (is_array($span)) { 28 | $span = implode("\n", $span); 29 | } 30 | 31 | // Replacing literal with tokens 32 | $span = preg_replace_callback('/``(.+)``(?!`)/mUsi', function($match) use (&$tokens) { 33 | $id = $this->generateToken(); 34 | $tokens[$id] = array( 35 | 'type' => 'literal', 36 | 'text' => htmlspecialchars($match[1]) 37 | ); 38 | 39 | return $id; 40 | }, $span); 41 | 42 | $environment = $parser->getEnvironment(); 43 | $this->environment = $environment; 44 | 45 | // Replacing numbering 46 | foreach ($environment->getTitleLetters() as $level => $letter) { 47 | $span = preg_replace_callback('/\#\\'.$letter.'/mUsi', function($match) use ($environment, $level) { 48 | return $environment->getNumber($level); 49 | }, $span); 50 | } 51 | 52 | // Signaling anonymous names 53 | $environment->resetAnonymousStack(); 54 | if (preg_match_all('/(([a-z0-9]+)|(`(.+)`))__/mUsi', $span, $matches)) { 55 | foreach ($matches[2] as $k => $y) { 56 | $name = $matches[2][$k] ?: $matches[4][$k]; 57 | $environment->pushAnonymous($name); 58 | } 59 | } 60 | 61 | // Looking for references to other documents 62 | $span = preg_replace_callback('/:([a-z0-9]+):`(.+)`/mUsi', function($match) use (&$environment, &$tokens) { 63 | $section = $match[1]; 64 | $url = $match[2]; 65 | $id = $this->generateToken(); 66 | $anchor = null; 67 | 68 | $text = null; 69 | if (preg_match('/^(.+)<(.+)>$/mUsi', $url, $match)) { 70 | $text = $match[1]; 71 | $url = $match[2]; 72 | } 73 | 74 | if (preg_match('/^(.+)#(.+)$/mUsi', $url, $match)) { 75 | $url = $match[1]; 76 | $anchor = $match[2]; 77 | } 78 | 79 | $tokens[$id] = array( 80 | 'type' => 'reference', 81 | 'section' => $section, 82 | 'url' => $url, 83 | 'text' => $text, 84 | 'anchor' => $anchor 85 | ); 86 | 87 | $environment->found($section, $url); 88 | 89 | return $id; 90 | }, $span); 91 | 92 | // Link callback 93 | $linkCallback = function($match) use ($environment, &$tokens) { 94 | $link = $match[2] ?: $match[4]; 95 | $id = $this->generateToken(); 96 | $next = $match[5]; 97 | $url = null; 98 | 99 | if (preg_match('/^(.+) <(.+)>$/mUsi', $link, $match)) { 100 | $link = $match[1]; 101 | $environment->setLink($link, $match[2]); 102 | $url = $match[2]; 103 | } 104 | 105 | $tokens[$id] = array( 106 | 'type' => 'link', 107 | 'link' => $link, 108 | 'url' => $url 109 | ); 110 | 111 | return $id.$next; 112 | }; 113 | 114 | // Replacing anonymous links 115 | $span = preg_replace_callback('/(([a-z0-9]+)|(`(.+)`))__([^a-z0-9]{1}|$)/mUsi', $linkCallback, $span); 116 | 117 | // Replacing links 118 | $span = preg_replace_callback('/(([a-z0-9]+)|(`(.+)`))_([^a-z0-9]{1}|$)/mUsi', $linkCallback, $span); 119 | 120 | $this->tokens = $tokens; 121 | $this->parser = $parser; 122 | $this->span = $span; 123 | } 124 | 125 | /** 126 | * Processes some data in the context of the span, this will process the 127 | * **emphasis**, the nbsp, replace variables and end-of-line brs 128 | */ 129 | public function process($data) 130 | { 131 | $self = $this; 132 | $environment = $this->parser->getEnvironment(); 133 | 134 | $span = $this->escape($data); 135 | 136 | // Emphasis 137 | $span = preg_replace_callback('/\*\*(.+)\*\*/mUsi', function ($matches) use ($self) { 138 | return $self->strongEmphasis($matches[1]); 139 | }, $span); 140 | $span = preg_replace_callback('/\*(.+)\*/mUsi', function ($matches) use ($self) { 141 | return $self->emphasis($matches[1]); 142 | }, $span); 143 | 144 | // Nbsp 145 | $span = preg_replace('/~/', $this->nbsp(), $span); 146 | 147 | // Replacing variables 148 | $span = preg_replace_callback('/\|(.+)\|/mUsi', function($match) use ($environment) { 149 | return $environment->getVariable($match[1]); 150 | }, $span); 151 | 152 | // Adding brs when a space is at the end of a line 153 | $span = preg_replace('/ \n/', $this->br(), $span); 154 | 155 | return $span; 156 | } 157 | 158 | /** 159 | * Renders the span 160 | */ 161 | public function render() 162 | { 163 | $environment = $this->parser->getEnvironment(); 164 | $span = $this->process($this->span); 165 | 166 | // Replacing tokens 167 | if ($this->tokens) { 168 | foreach ($this->tokens as $id => $value) { 169 | switch ($value['type']) { 170 | case 'raw': 171 | $span = str_replace($id, $value['text'], $span); 172 | break; 173 | case 'literal': 174 | $span = str_replace($id, $this->literal($value['text']), $span); 175 | break; 176 | case 'reference': 177 | $reference = $environment->resolve($value['section'], $value['url']); 178 | $link = $this->reference($reference, $value); 179 | 180 | $span = str_replace($id, $link, $span); 181 | break; 182 | case 'link': 183 | if ($value['url']) { 184 | if ($environment->useRelativeUrls()) { 185 | $url = $environment->relativeUrl($value['url']); 186 | } else { 187 | $url = $value['url']; 188 | } 189 | } else { 190 | $url = $environment->getLink($value['link']); 191 | } 192 | $link = $this->link($url, $this->process($value['link'])); 193 | $span = str_replace($id, $link, $span); 194 | break; 195 | } 196 | } 197 | } 198 | 199 | return $span; 200 | } 201 | 202 | public function emphasis($text) 203 | { 204 | return $text; 205 | } 206 | 207 | public function strongEmphasis($text) 208 | { 209 | return $text; 210 | } 211 | 212 | public function nbsp() 213 | { 214 | return ' '; 215 | } 216 | 217 | public function br() 218 | { 219 | return "\n"; 220 | } 221 | 222 | public function literal($text) 223 | { 224 | return $text; 225 | } 226 | 227 | public function link($url, $title) 228 | { 229 | return $title.' ('.$url.')'; 230 | } 231 | 232 | public function escape($span) 233 | { 234 | return $span; 235 | } 236 | 237 | public function reference($reference, $value) 238 | { 239 | if ($reference) { 240 | $text = $value['text'] ?: (isset($reference['title']) ? $reference['title'] : ''); 241 | $link = $this->link($url, trim($text)); 242 | } else { 243 | $link = $this->link('#', '(unresolved reference)'); 244 | } 245 | 246 | return $link; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /SubDirective.php: -------------------------------------------------------------------------------- 1 | getSubParser(); 25 | 26 | if ($node instanceof CodeNode) { 27 | $document = $subParser->parseLocal($node->getValue()); 28 | } else { 29 | $document = $node; 30 | } 31 | 32 | $newNode = $this->processSub($parser, $document, $variable, $data, $options); 33 | 34 | if ($newNode) { 35 | if ($variable) { 36 | $parser->getEnvironment()->setVariable($variable, $newNode); 37 | } else { 38 | $parser->getDocument()->addNode($newNode); 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * Process a sub directive 45 | */ 46 | public function processSub(Parser $parser, $document, $variable, $data, array $options) 47 | { 48 | return null; 49 | } 50 | 51 | public function wantCode() 52 | { 53 | return true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /autoload.php: -------------------------------------------------------------------------------- 1 | =5.3.0", 16 | "symfony/polyfill-mbstring": "^1.12" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "^6.4" 20 | }, 21 | "target-dir": "Gregwar/RST", 22 | "autoload": { 23 | "psr-0": { 24 | "Gregwar\\RST": "" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./tests/ParserTests.php 17 | ./tests/EnvironmentTests.php 18 | ./tests/HTMLTests.php 19 | ./tests/BuilderTests.php 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/document/demo.php: -------------------------------------------------------------------------------- 1 | `_ 18 | '; 19 | 20 | $document = $parser->parse($rst); 21 | 22 | echo $document; 23 | /* Will output: 24 |

    Hello world

    25 |

    What is it?

    26 |

    This is a RST document!

    27 |

    Where can I get it?

    28 |

    You can get it on the GitHub page

    29 | */ 30 | -------------------------------------------------------------------------------- /test/document/document.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo document 6 | 7 | 8 | 9 | 10 | 11 |

    Gregwar/RST Sandbox

    12 |

    Reference to HoHo

    13 |

    Note:
    This is a testing sandbox, if you want to understand how it works, have a 14 | look to the rst original file

    15 |
    16 |

    1) Titles

    17 |

    1) Using titles

    18 |

    Titles can be wrote using the underlining, the default order is:

    19 |
    1. =======
    2. 20 |
    3. -------
    4. 21 |
    5. ~~~~~~~
    6. 22 |
    7. *******
    8. 23 |
    24 | 25 |

    2) Using auto-numbering

    26 |

    This is not a standard of RST but is really useful, you can use the special syntax 27 | # followed by the letter of the title you are in (it resets the counter when used).

    28 |

    You can for instance use Question #* if you are under an *******, the number 29 | displayed will be auto-incremented:

    30 |

    Question #*

    31 |

    The first question

    32 |

    Question #*

    33 |

    The second question

    34 |

    This can of course also be used to number parts, chapter etc.

    35 |

    3) Separator

    36 |

    A separator is like a title underline but without any text above:

    37 |
    -----
     38 | 
    39 |

    This will result in a text separation:

    40 |
    41 |

    2) Inline style

    42 | 45 | 46 |

    You can force a line break by adding an extra space at the end of a line

    47 |

    3) Tables

    48 |

    Tables can be created using the line separator ====:

    49 |
    ================     ================
     50 | **First column**     **Other column**
     51 | ================     ================
     52 | Second row with      Second row, of the
     53 | some contents text   other column
     54 | ============         ================
     55 | 
    56 |

    Will result in:

    57 |
    First columnOther column
    Second row with some contents textSecond row, of the other column
    58 |

    Another example:

    59 |
    Col ACol BCol C
    Col XCol YCol Z
    Col UCol JCol K
    60 |

    4) Lists

    61 |

    Lists can be ordered or unordered, and nested, for instance this:

    62 |
    * Element A
     63 |     * Sub A, this a
     64 |       multiline sentence in the source
     65 |         1. Sub ordered as "1"
     66 |         2. Sub ordered as "2"
     67 |     * Sub hello two
     68 | * Element B
     69 | 
    70 |

    While result in:

    71 | 81 | 82 |

    5) Blocks

    83 |

    1) Quoting

    84 |

    You can quote a block by indenting it:

    85 |
    This is a normal pagagraph
     86 | 
     87 |     This is a quote
     88 | 
    89 |

    Will result in:

    90 |

    This is a normal paragraph

    91 |

    This is a quote

    92 |
    93 |

    2) Code

    94 |

    You can quote code the same way as quote, but using the :: at the end 95 | of the previous paragraph:

    96 |
    Here is a piece of code:
     97 | 
     98 | .. code-block:: php
     99 | 
    100 |     <?php
    101 | 
    102 |     echo "I love RST";
    103 | 
    104 |

    Will result in:

    105 |

    Here is a piece of code:

    106 |
    <?php
    107 | 
    108 | echo "I love RST";
    109 | 
    110 |

    6) Links

    111 |

    1) Standard links

    112 |

    Links can be defined once for all using the trailing _, like this:

    113 |
    PHP_ is a great language
    114 | 
    115 | .. _PHP: http://php.net/
    116 | 
    117 |

    Will result in:

    118 |

    PHP is a great language

    119 |

    2) Anonymous links

    120 |

    Anonymous links can also be used to avoid copying the name just after the 121 | block that uses it, for instance:

    122 |
    I love GitHub__
    123 | 
    124 | .. __: http://www.github.com/
    125 | 
    126 |

    Will result in:

    127 |

    I love GitHub

    128 |

    You can use the following shortcut:

    129 |
    I love GitHub__
    130 | 
    131 | __ http://www.github.com/
    132 | 
    133 |

    3) Inline links

    134 |

    You can also define the link target inside the link:

    135 |
    Do you know `Google <http://www.google.com>`_ ?
    136 | 
    137 |

    Will result in:

    138 |

    Do you know Google ?

    139 |

    4) Anchor links

    140 |

    An anchor can be used like this:

    141 |
    .. _anchor:
    142 | 
    143 | Some anchor section, you can link to it like `this <#anchor>`_
    144 | 
    145 |

    Will result in:

    146 | 147 |

    Some anchor section, you can link to it like this

    148 |

    7) Directives

    149 |

    1) Include

    150 |

    You can include a file with the include pseudo-directive:

    151 |
    .. include:: file.rst
    152 | 
    153 |

    2) Replace

    154 |

    You can use the replace directive like this:

    155 |
    .. |name| replace:: bob
    156 | 
    157 | Hello |name| !
    158 | 
    159 |

    Will result in:

    160 |

    Hello bob !

    161 |

    3) Image

    162 |

    The image directive can be used to display images, this way:

    163 |
    .. image:: rst.png
    164 |     :width: 250px
    165 |     :title: RST logo
    166 | 
    167 |

    Will result in:

    168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /test/document/document.rst: -------------------------------------------------------------------------------- 1 | .. document:: 2 | .. title:: Demo document 3 | .. stylesheet:: style.css 4 | .. meta:: 5 | :description: A demo Gregwar/RST document 6 | 7 | .. .. this is a comment! 8 | 9 | Gregwar/RST Sandbox 10 | =================== 11 | 12 | Reference to :doc:`HoHo ` 13 | 14 | .. note:: 15 | **Note**: 16 | This is a testing sandbox, if you want to understand how it works, have a 17 | look to the rst original file 18 | 19 | #=) Titles 20 | ---------- 21 | 22 | #-) Using titles 23 | ~~~~~~~~~~~~~~~~ 24 | 25 | Titles can be wrote using the underlining, the default order is: 26 | 27 | 1. ``=======`` 28 | 2. ``-------`` 29 | 3. ``~~~~~~~`` 30 | 4. ``*******`` 31 | 32 | #-) Using auto-numbering 33 | ~~~~~~~~~~~~~~~~~~~~~~~~ 34 | 35 | This is not a standard of RST but is really useful, you can use the special syntax 36 | ``#`` followed by the letter of the title you are in (it resets the counter when used). 37 | 38 | You can for instance use ``Question #*`` if you are under an ``*******``, the number 39 | displayed will be auto-incremented: 40 | 41 | ** Question #* ** 42 | 43 | The first question 44 | 45 | ** Question #* ** 46 | 47 | The second question 48 | 49 | This can of course also be used to number parts, chapter etc. 50 | 51 | 52 | #-) Separator 53 | ~~~~~~~~~~~~~ 54 | 55 | A separator is like a title underline but without any text above:: 56 | 57 | ----- 58 | 59 | This will result in a text separation: 60 | 61 | ---- 62 | 63 | #=) Inline style 64 | ---------------- 65 | 66 | * ``*italic*`` renders as *italic* 67 | * ``**strong**`` renders as **strong** 68 | 69 | You can force a line break by adding an extra space at the end of a line 70 | 71 | #=) Tables 72 | ---------- 73 | 74 | Tables can be created using the line separator ``====``:: 75 | 76 | ================ ================ 77 | **First column** **Other column** 78 | ================ ================ 79 | Second row with Second row, of the 80 | some contents text other column 81 | ============ ================ 82 | 83 | Will result in: 84 | 85 | ================ ================ 86 | **First column** **Other column** 87 | ================ ================ 88 | Second row with Second row, of the 89 | some contents text other column 90 | ============ ================ 91 | 92 | Another example: 93 | 94 | === === === 95 | Col A Col B Col C 96 | === === === 97 | Col X Col Y Col Z 98 | === === === 99 | Col U Col J Col K 100 | === === === 101 | 102 | #=) Lists 103 | --------- 104 | 105 | Lists can be ordered or unordered, and nested, for instance this:: 106 | 107 | * Element A 108 | * Sub A, this a 109 | multiline sentence in the source 110 | 1. Sub ordered as "1" 111 | 2. Sub ordered as "2" 112 | * Sub hello two 113 | * Element B 114 | 115 | While result in: 116 | 117 | * Element A 118 | * Sub A, this a 119 | multiline sentence in the source 120 | 1. Sub ordered as "1" 121 | 2. Sub ordered as "2" 122 | * Sub hello two 123 | * Element B 124 | 125 | #=) Blocks 126 | ---------- 127 | 128 | #-) Quoting 129 | ~~~~~~~~~~~ 130 | 131 | You can quote a block by indenting it:: 132 | 133 | This is a normal pagagraph 134 | 135 | This is a quote 136 | 137 | Will result in: 138 | 139 | This is a normal paragraph 140 | 141 | This is a quote 142 | 143 | #-) Code 144 | ~~~~~~~~ 145 | 146 | You can quote code the same way as quote, but using the ``::`` at the end 147 | of the previous paragraph:: 148 | 149 | Here is a piece of code: 150 | 151 | .. code-block:: php 152 | 153 | `_ ? 213 | 214 | Will result in: 215 | 216 | Do you know `Google `_ ? 217 | 218 | #-) Anchor links 219 | ~~~~~~~~~~~~~~~~ 220 | 221 | An anchor can be used like this:: 222 | 223 | .. _anchor: 224 | 225 | Some anchor section, you can link to it like `this <#anchor>`_ 226 | 227 | Will result in: 228 | 229 | .. _anchor: 230 | 231 | Some anchor section, you can link to it like `this <#anchor>`_ 232 | 233 | #=) Directives 234 | -------------- 235 | 236 | #-) Include 237 | ~~~~~~~~~~~ 238 | 239 | .. include:: include.rst 240 | 241 | #-) Replace 242 | ~~~~~~~~~~~ 243 | 244 | You can use the replace directive like this:: 245 | 246 | .. |name| replace:: bob 247 | 248 | Hello |name| ! 249 | 250 | Will result in: 251 | 252 | .. |name| replace:: bob 253 | 254 | Hello |name| ! 255 | 256 | #-) Image 257 | ~~~~~~~~~ 258 | 259 | The ``image`` directive can be used to display images, this way:: 260 | 261 | .. image:: rst.png 262 | :width: 250px 263 | :title: RST logo 264 | 265 | Will result in: 266 | 267 | .. image:: rst.png 268 | :width: 250px 269 | :title: RST logo 270 | -------------------------------------------------------------------------------- /test/document/include.rst: -------------------------------------------------------------------------------- 1 | 2 | You can include a file with the include pseudo-directive:: 3 | 4 | .. include:: file.rst 5 | -------------------------------------------------------------------------------- /test/document/latex.php: -------------------------------------------------------------------------------- 1 | parse(file_get_contents('document.rst')); 10 | 11 | echo $document->renderDocument(); 12 | -------------------------------------------------------------------------------- /test/document/parse.php: -------------------------------------------------------------------------------- 1 | parse(file_get_contents('document.rst')); 9 | 10 | echo $document->renderDocument(); 11 | -------------------------------------------------------------------------------- /test/document/rst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gregwar/RST/50b34c9f43facdeaa7e99745ca57dc17b418b1d8/test/document/rst.png -------------------------------------------------------------------------------- /test/document/style.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | font-family:Sans-serif; 4 | font-size:14px; 5 | width:900px; 6 | margin:auto; 7 | } 8 | 9 | h1 { 10 | border-bottom:2px solid #666; 11 | } 12 | 13 | h2 { 14 | margin-left:10px; 15 | border-bottom:1px dotted #aaa; 16 | } 17 | 18 | h3 { 19 | margin-left:20px; 20 | } 21 | 22 | h4 { 23 | margin-left:30px; 24 | } 25 | 26 | a { 27 | color:#c00; 28 | } 29 | 30 | code { 31 | border:1px solid #ddd; 32 | border-radius:3px; 33 | font-family:Courier; 34 | font-size:12px; 35 | background-color:#fafafa; 36 | color:#333; 37 | } 38 | 39 | pre code { 40 | border:none; 41 | background-color:transparent; 42 | color:#eee; 43 | } 44 | 45 | pre { 46 | padding:5px; 47 | border-radius:10px; 48 | font-weight:bold; 49 | background-color:black; 50 | } 51 | 52 | .note { 53 | color:#5555ff; 54 | background-color:#e9e9ff; 55 | padding:5px; 56 | border-radius:4px; 57 | } 58 | 59 | .note * { 60 | margin:0; 61 | } 62 | 63 | blockquote { 64 | padding:5px; 65 | padding-left:10px; 66 | border-left:15px solid #eee; 67 | font-style:italic; 68 | } 69 | 70 | table td { 71 | border: 1px solid black; 72 | padding:5px; 73 | } 74 | 75 | table { 76 | border-collapse: collapse; 77 | } 78 | -------------------------------------------------------------------------------- /test/tree/.gitignore: -------------------------------------------------------------------------------- 1 | output 2 | -------------------------------------------------------------------------------- /test/tree/input/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | width:1000px; 3 | margin:auto; 4 | } 5 | -------------------------------------------------------------------------------- /test/tree/input/include.rst: -------------------------------------------------------------------------------- 1 | 2 | Hello world! 3 | -------------------------------------------------------------------------------- /test/tree/input/index.rst: -------------------------------------------------------------------------------- 1 | 2 | .. .. test:: 3 | 4 | Index file 5 | ========== 6 | 7 | Testing `xkcd`_ ! 8 | 9 | This is the index of all! 10 | 11 | .. toctree:: 12 | :depth: 3 13 | 14 | otherpage 15 | subdir/testing_sub 16 | special 17 | 18 | Testing reference in a list: 19 | 20 | * :doc:`subdir/testing_sub` 21 | 22 | Including 23 | --------- 24 | 25 | .. include:: include.rst 26 | 27 | .. _`xkcd`: http://xkcd.com/ 28 | 29 | -------------------------------------------------------------------------------- /test/tree/input/otherpage.rst: -------------------------------------------------------------------------------- 1 | Other page 2 | ========== 3 | 4 | This is another page 5 | 6 | Lorem ipsum 7 | 8 | Lorem ipsum 9 | 10 | 11 | Lorem ipsum 12 | 13 | Lorem ipsum 14 | 15 | 16 | Lorem ipsum 17 | 18 | Lorem ipsum 19 | 20 | 21 | Lorem ipsum 22 | 23 | Lorem ipsum 24 | 25 | 26 | Lorem ipsum 27 | 28 | Lorem ipsum 29 | 30 | 31 | Lorem ipsum 32 | 33 | Lorem ipsum 34 | 35 | 36 | Lorem ipsum 37 | 38 | Lorem ipsum 39 | 40 | 41 | Lorem ipsum 42 | 43 | Lorem ipsum 44 | 45 | 46 | First title 47 | ----------- 48 | 49 | First sub title 50 | ~~~~~~~~~~~~~~~ 51 | 52 | Second title 53 | ------------ 54 | 55 | Second sub title 56 | ~~~~~~~~~~~~~~~~ 57 | 58 | Third sub title 59 | ~~~~~~~~~~~~~~~ 60 | 61 | .. redirection-title:: special 62 | 63 | Fourth sub title 64 | ~~~~~~~~~~~~~~~~ 65 | -------------------------------------------------------------------------------- /test/tree/input/special.rst: -------------------------------------------------------------------------------- 1 | 2 | Special 3 | ======= 4 | 5 | .. url:: something-special 6 | 7 | This page has a special URL! 8 | -------------------------------------------------------------------------------- /test/tree/input/subdir/testinclude.rst: -------------------------------------------------------------------------------- 1 | 2 | Hello you! 3 | -------------------------------------------------------------------------------- /test/tree/input/subdir/testing_sub.rst: -------------------------------------------------------------------------------- 1 | Subdir testing 2 | ============== 3 | 4 | This is a file in the ``subdir`` directory 5 | 6 | Back to the :doc:`/index` ? 7 | 8 | This is the :doc:`testing_sub` page 9 | 10 | Testing include: 11 | 12 | .. include:: testinclude.rst 13 | -------------------------------------------------------------------------------- /test/tree/latex.php: -------------------------------------------------------------------------------- 1 | copy('css', 'css'); 13 | $builder->build('input', 'output'); 14 | } 15 | catch (\Exception $exception) 16 | { 17 | echo "\n"; 18 | echo "Error: ".$exception->getMessage()."\n"; 19 | } 20 | -------------------------------------------------------------------------------- /test/tree/parse.php: -------------------------------------------------------------------------------- 1 | copy('css', 'css'); 12 | $builder->build('input', 'output'); 13 | } 14 | catch (\Exception $exception) 15 | { 16 | echo "\n"; 17 | echo "Error: ".$exception->getMessage()."\n"; 18 | } 19 | -------------------------------------------------------------------------------- /tests/BuilderTests.php: -------------------------------------------------------------------------------- 1 | assertTrue(is_dir($this->targetFile())); 19 | $this->assertTrue(file_exists($this->targetFile('index.html'))); 20 | $this->assertTrue(file_exists($this->targetFile('introduction.html'))); 21 | $this->assertTrue(file_exists($this->targetFile('subdirective.html'))); 22 | $this->assertTrue(file_exists($this->targetFile('magic-link.html'))); 23 | $this->assertTrue(file_exists($this->targetFile('file.txt'))); 24 | $this->assertTrue(file_exists($this->targetFile('subdir/test.html'))); 25 | } 26 | 27 | /** 28 | * Tests the ..url :: directive 29 | */ 30 | public function testUrl() 31 | { 32 | $contents = file_get_contents($this->targetFile('index.html')); 33 | 34 | $this->assertContains('"magic-link.html', $contents); 35 | $this->assertContains('Another page', $contents); 36 | } 37 | 38 | /** 39 | * Tests the links 40 | */ 41 | public function testLinks() 42 | { 43 | $contents = file_get_contents($this->targetFile('subdir/test.html')); 44 | 45 | $this->assertContains('"../to/resource"', $contents); 46 | $this->assertContains('"http://absolute/"', $contents); 47 | 48 | $this->assertContains('"http://google.com"', $contents); 49 | $this->assertContains('"http://yahoo.com"', $contents); 50 | 51 | $this->assertEquals(2, substr_count($contents, 'http://something.com')); 52 | } 53 | 54 | /** 55 | * Tests that the index toctree worked 56 | */ 57 | public function testToctree() 58 | { 59 | $contents = file_get_contents($this->targetFile('index.html')); 60 | 61 | $this->assertContains('introduction.html', $contents); 62 | $this->assertContains('Introduction page', $contents); 63 | } 64 | 65 | /** 66 | * Testing references to other documents 67 | */ 68 | public function testReferences() 69 | { 70 | $contents = file_get_contents($this->targetFile('introduction.html')); 71 | 72 | $this->assertContains('Index, paragraph toc', $contents); 73 | $this->assertContains('Index', $contents); 74 | $this->assertContains('Summary', $contents); 75 | 76 | $contents = file_get_contents($this->targetFile('subdir/test.html')); 77 | 78 | $this->assertContains('"../index.html"', $contents); 79 | } 80 | 81 | /** 82 | * Testing wrapping sub directive 83 | */ 84 | public function testSubDirective() 85 | { 86 | $contents = file_get_contents($this->targetFile('subdirective.html')); 87 | 88 | $this->assertEquals(2, substr_count($contents, '
    ')); 89 | $this->assertEquals(2, substr_count($contents, '
  • ')); 90 | $this->assertContains('
  • ', $contents); 91 | $this->assertEquals(2, substr_count($contents, '')); 92 | $this->assertEquals(1, substr_count($contents, '')); 94 | $this->assertContains('

    This is a simple note!

    ', $contents); 95 | $this->assertContains('

    There is a title here

    ', $contents); 96 | } 97 | 98 | /** 99 | * Test that redirection-title worked 100 | */ 101 | public function testRedirectionTitle() 102 | { 103 | $contents = file_get_contents($this->targetFile('magic-link.html')); 104 | $this->assertNotContains('redirection', $contents); 105 | 106 | $contents = file_get_contents($this->targetFile('index.html')); 107 | $this->assertContains('"subdirective.html">See also', $contents); 108 | } 109 | 110 | public function setUp() 111 | { 112 | shell_exec('rm -rf '.$this->targetFile()); 113 | $builder = new Builder; 114 | $builder->copy('file.txt'); 115 | $builder->setUseRelativeUrls(true); 116 | $builder->build($this->sourceFile(), $this->targetFile(), false); 117 | } 118 | 119 | protected function sourceFile($file = '') 120 | { 121 | return __DIR__.'/builder/input/'.$file; 122 | } 123 | 124 | protected function targetFile($file = '') 125 | { 126 | return __DIR__.'/builder/output/'.$file; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/EnvironmentTests.php: -------------------------------------------------------------------------------- 1 | setCurrentFileName('path/to/something.rst'); 14 | $environment->setCurrentDirectory('input/dir'); 15 | 16 | // Assert that rules of relative url are respected 17 | $this->assertEquals($environment->relativeUrl('test.jpg'), 'test.jpg'); 18 | $this->assertEquals($environment->relativeUrl('/path/to/test.jpg'), 'test.jpg'); 19 | $this->assertEquals($environment->relativeUrl('/path/x/test.jpg'), '../../path/x/test.jpg'); 20 | $this->assertEquals($environment->relativeUrl('/test.jpg'), '../../test.jpg'); 21 | $this->assertEquals($environment->relativeUrl('http://example.com/test.jpg'), 'http://example.com/test.jpg'); 22 | $this->assertEquals($environment->relativeUrl('imgs/test.jpg'), 'imgs/test.jpg'); 23 | $this->assertEquals($environment->relativeUrl('/imgs/test.jpg'), '../../imgs/test.jpg'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/HTMLTests.php: -------------------------------------------------------------------------------- 1 | parseHTML('links.rst'); 17 | 18 | $this->assertContains('', $document); 19 | $this->assertContains('', $document); 20 | $this->assertContains('', $document); 21 | $this->assertContains('', $document); 22 | $this->assertContains('', $document); 23 | $this->assertContains('under_score', $document); 24 | $this->assertContains(' spacy', $document); 25 | $this->assertNotContains(' ,', $document); 26 | $this->assertNotContains('`', $document); 27 | } 28 | 29 | /** 30 | * Testing the non breakable spaces (~) 31 | */ 32 | public function testNbsp() 33 | { 34 | $document = $this->parseHTML('nbsp.rst'); 35 | 36 | $this->assertContains(' ', $document); 37 | $this->assertNotContains('~', $document); 38 | } 39 | 40 | /** 41 | * Testing that the text is ecaped 42 | */ 43 | public function testEscape() 44 | { 45 | $document = $this->parseHTML('escape.rst'); 46 | 47 | $this->assertContains('<script>', $document); 48 | $this->assertNotContains('