diff --git a/ParserKrokiTag.php b/ParserKrokiTag.php
new file mode 100644
index 0000000..4405464
--- /dev/null
+++ b/ParserKrokiTag.php
@@ -0,0 +1,366 @@
+parserOptions = $parserOptions;
+ $this->parserOutput = $parserOutput;
+ $this->language = $parser->getTargetLanguage();
+ }
+
+ /**
+ * Check if $string is one of the available diagram types.
+ *
+ * @param string $string
+ * @return bool
+ */
+ private static function validDiagrammType( string $string ): bool {
+ return in_array( strtolower( $string ), array_map( 'strtolower', self::DIAGRAM_TYPES ), true );
+ }
+
+ /**
+ * - Add tracking categories
+ * - Split parser cache for preview, where Graph uses different HTML
+ * @param ParserOutput $parserOutput
+ * @param ?PageReference $pageRef
+ * @param bool $isPreview
+ */
+ public static function addTagMetadata(
+ ParserOutput $parserOutput, ?PageReference $pageRef, bool $isPreview
+ ): void {
+ $tc = MediaWikiServices::getInstance()->getTrackingCategories();
+ if ( $parserOutput->getExtensionData( 'kroki_diagram_broken' ) ) {
+ $tc->addTrackingCategory( $parserOutput, 'kroki-diagram-broken-category', $pageRef );
+ }
+ $tc->addTrackingCategory( $parserOutput, 'kroki-diagram-tracking-category', $pageRef );
+
+ if ( $isPreview ) {
+ $parserOutput->updateCacheExpiry( 0 );
+ }
+ }
+
+ /**
+ * Handles the onKrokiTag hook.
+ *
+ * @param string|null $input The input string for the tag.
+ * @param array $args An array of arguments passed to the tag.
+ * @param Parser $parser The Parser object for the current page.
+ * @param PPFrame $frame The PPFrame object for the current page.
+ * @return Status|string The generated HTML or a Status object if there was an error.
+ */
+ public static function onKrokiTag( $input, $args, $parser, $frame ) {
+ $tag = new self( $parser, $parser->getOptions(), $parser->getOutput() );
+
+ $input = $parser->getStripState()->unstripNoWiki( $input ?? '' );
+
+ $html = $tag->buildHtmlInline( (string)$input, $args );
+ self::addTagMetadata( $parser->getOutput(), $parser->getPage(), $parser->getOptions()->getIsPreview() );
+ return $html;
+ }
+
+ /**
+ * Returns the kroki.io server url
+ *
+ * @return string
+ */
+ private static function getKrokiUrl(): string {
+ return MediaWikiServices::getInstance()->getMainConfig()->get( 'KrokiServerEndpoint' );
+ }
+
+ /**
+ * Formats the HTML for displaying an error message.
+ *
+ * @param \Message $wfMessage The error message to be displayed.
+ * @param string $data_string The data string associated with the error (optional).
+ * @param string $response_string The response string associated with the error (optional).
+ * @return string The formatted HTML code for the error message.
+ */
+ private function formatError( \Message $wfMessage, string $data_string = '', string $response_string = '' ): string {
+ $this->parserOutput->setExtensionData( 'kroki_diagram_broken', true );
+ $error = $wfMessage->inLanguage( $this->language )->parse();
+
+ if ( !empty( $response_string ) ) {
+ $contents = ( 'HTTP-Response: ' . PHP_EOL .
+ $response_string . PHP_EOL . PHP_EOL )
+ . 'Diagram-Code:' . $data_string;
+ } else {
+ $contents = 'Diagram-Code:' . $data_string;
+ }
+
+ return Html::rawElement(
+ 'div',
+ [ 'class' => self::KROKI_CSS_CLASS ],
+ Html::rawElement(
+ 'pre',
+ [
+ 'data-title' => $error,
+ ],
+ $contents
+ )
+ );
+ }
+
+ /**
+ * Generates the HTML for the 'buildHtml_inline' method.
+ *
+ * @param string $input The input string for the tag.
+ * @param null $args An array of arguments passed to the tag.
+ * @return string The generated HTML or a Status object if there was an error.
+ */
+ public function buildHtmlInline( string $input, $args = null ): string {
+ if ( $input === '' ) {
+ return $this->formatError( wfMessage( 'kroki-error-empty-input' ) );
+ }
+
+ $lang = $args['lang'] ?? '';
+
+ // For invalid diagram type, output nothing instead of broken img.
+ if ( !self::validDiagrammType( $lang ) ) {
+ return $this->formatError( wfMessage( 'kroki-error-unknown-language' ) );
+ }
+
+ $output_format = $args['output_format'] ?? 'svg';
+
+ //TODO: add check for format onlt png or svg
+
+ //DEBUG
+ echo $output_format;
+
+ // set post fields
+ $requestParams = [
+ 'method' => 'POST',
+ 'postData' => json_encode( [
+ 'diagram_source' => $input,
+ 'diagram_type' => $lang,
+ 'output_format' => $output_format,
+ 'diagram_options' => [
+ 'no-doctype' => 'TRUE'
+ ] ] ),
+ ];
+
+ $url = self::getKrokiUrl();
+
+ // Create HttpRequest
+ $request = MediaWikiServices::getInstance()->getHttpRequestFactory()->create( $url, $requestParams, __METHOD__ );
+
+ // Send the request
+ $status = $request->execute();
+
+ if ( !$status->isOK() ) {
+ return $this->formatError( wfMessage( 'kroki-error-diagram-render-failed' ), $input, $request->getContent() );
+ }
+
+ if ( $request->getContent() === '' ) {
+ return $this->formatError( wfMessage( 'kroki-error-diagram-render-failed' ), $input );
+ }
+
+ $width = $args['width'] ?? '';
+ $height = $args['height'] ?? '';
+
+ return self::formatHtml( "data:image/" . $output_format . "+xml;base64," . base64_encode( $request->getContent() ), $width, $height, $lang );
+ }
+
+ /**
+ * Builds the HTML for a local file diagram.
+ *
+ * @param string $input The input string for the diagram.
+ * @param null $args An array of additional arguments passed to the diagram.
+ * @return Status|string The generated HTML or a Status object if there was an error.
+ * @throws \MWException
+ */
+ public function buildHtmlLocalfile( string $input, $args = null ) {
+ if ( $input === '' ) {
+ return $this->formatError( wfMessage( 'kroki-error-empty-input' ) );
+ }
+
+ $lang = $args['lang'] ?? '';
+
+ // For invalid diagram type, output nothing instead of broken img.
+ if ( !self::validDiagrammType( $lang ) ) {
+ return $this->formatError( wfMessage( 'kroki-error-unknown-language' ) );
+ }
+
+ $output_format = $args['output_format'] ?? 'svg';
+
+ //TODO: add check for format onlt png or svg
+
+ //DEBUG
+ echo $output_format;
+
+ // set post fields
+ $requestParams = [
+ 'method' => 'POST',
+ 'postData' => json_encode( [
+ 'diagram_source' => $input,
+ 'diagram_type' => $lang,
+ 'output_format' => $output_format,
+ 'diagram_options' => [
+ 'no-doctype' => 'TRUE'
+ ] ], JSON_FORCE_OBJECT ),
+ ];
+
+ $url = self::getKrokiUrl();
+
+ // Create HttpRequest
+ $request = MediaWikiServices::getInstance()->getHttpRequestFactory()->create( $url, $requestParams, __METHOD__ );
+
+ // Send the request
+ $status = $request->execute();
+
+ if ( !$status->isOK() ) {
+ return $this->formatError( wfMessage( 'kroki-error-diagram-render-failed' ), $input, $request->getContent() );
+ }
+
+ if ( $request->getContent() === '' ) {
+ return $this->formatError( wfMessage( 'kroki-error-diagram-render-failed' ), $input );
+ }
+
+ $result = $request->getContent();
+
+ $localRepo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
+ $diagramsRepo = new LocalRepo( [
+ 'class' => 'LocalRepo',
+ 'name' => 'local',
+ 'backend' => $localRepo->getBackend(),
+ 'directory' => $localRepo->getZonePath( 'public' ) . '/kroki_diagrams',
+ 'url' => $localRepo->getZoneUrl( 'public' ) . '/kroki_diagrams',
+ 'hashLevels' => 0,
+ 'thumbUrl' => '',
+ 'transformVia404' => false,
+ 'deletedDir' => '',
+ 'deletedHashLevels' => 0,
+ 'zones' => [
+ 'public' => [
+ 'directory' => '/kroki_diagrams',
+ ],
+ ],
+ ] );
+
+ $sha1Input = sha1( $input );
+ $fileName = implode( '_', [ 'Kroki', $lang, $sha1Input ] ) . '.' . $output_format;
+ $graphFile = $diagramsRepo->findFile( $fileName );
+
+ if ( !$graphFile ) {
+ $graphFile = $diagramsRepo->newFile( $fileName );
+ }
+
+ if ( $graphFile->exists() ) {
+ $width = $args['width'] ?? '';
+ $height = $args['height'] ?? '';
+
+ return self::formatHtml( $graphFile->getUrl(), $width, $height, $lang );
+ }
+
+ $tmpFactory = MediaWikiServices::getInstance()->getTempFSFileFactory();
+ $tmpGraphSourceFile = $tmpFactory->newTempFSFile( 'diagrams_out', $sha1Input );
+ file_put_contents( $tmpGraphSourceFile->getPath(), $result );
+
+ if ( $this->parserOptions->getIsPreview() ) {
+ $check = $diagramsRepo->storeTemp( $fileName, $tmpGraphSourceFile );
+ } else {
+ $check = $graphFile->publish( $tmpGraphSourceFile );
+ }
+
+ if ( !$check->isGood() ) {
+ $status->value = $this->formatError( $check->getHtml() );
+ return $status;
+ }
+
+ $width = $args['width'] ?? '';
+ $height = $args['height'] ?? '';
+
+ return self::formatHtml( $graphFile->getUrl(), $width, $height, $lang );
+ }
+
+ /**
+ * Formats the HTML for displaying an image.
+ *
+ * @param string $imgUrl The URL of the image.
+ * @return string The formatted HTML code.
+ */
+ private static function formatHtml( string $imgUrl, string $width, string $height, string $lang ): string {
+ return Html::rawElement(
+ 'div',
+ [ 'class' => self::KROKI_CSS_CLASS ],
+ Html::element( 'img', [ 'src' => $imgUrl, 'width' => $width, 'height' => $height, 'style' => 'background-color: rgba(150, 150, 150, 0.5);' ] )
+ );
+ }
+
+ /**
+ * Finalizes the parser output for the Kroki extension.
+ *
+ * @param OutputPage $outputPage The OutputPage object representing the rendered page.
+ * @param ParserOutput $parserOutput The ParserOutput object for the current page.
+ * @return void
+ */
+ public static function finalizeParserOutput(
+ OutputPage $outputPage, ParserOutput $parserOutput
+ ): void {
+ $outputPage->addModuleStyles( [ 'ext.kroki.styles' ] );
+ }
+}
\ No newline at end of file