This is the content of the details.
` HastElements with Confluence's
+ * structured code macro format.
+ *
+ * @see {@link https://developer.atlassian.com/server/confluence/confluence-storage-format/ | Confluence Storage Format }
+ *
+ * @example
+ * const x = 42;
+ * // becomes
+ *
+ * javascript
+ *
+ *
+ */
+const rehypeReplaceCodeBlocks: UnifiedPlugin<[], Root> =
+ function rehypeReplaceCodeBlocks() {
+ return function transformer(tree) {
+ replace(tree, { type: "element", tagName: "pre" }, (node) => {
+ // Check if this pre element contains a code element
+ const codeElement = node.children.find(
+ (child) =>
+ child.type === "element" &&
+ (child as HastElement).tagName === "code",
+ ) as HastElement | undefined;
+
+ if (!codeElement) {
+ // If there's no code element, return the pre element unchanged
+ return node;
+ }
+
+ // Extract the language from the code element's className
+ const language = extractLanguage(codeElement);
+
+ // Extract the text content from the code element
+ const codeContent = extractTextContent(codeElement);
+
+ // Build the Confluence code macro
+ const macroChildren: HastElement[] = [];
+
+ // Add language parameter if present
+ if (language) {
+ macroChildren.push({
+ type: "element" as const,
+ tagName: "ac:parameter",
+ properties: {
+ "ac:name": "language",
+ },
+ children: [
+ {
+ type: "text" as const,
+ value: language,
+ },
+ ],
+ });
+ }
+
+ // Add the code content
+ // Note: We use a text node with the raw CDATA markup
+ // The rehypeStringify with allowDangerousHtml will preserve it
+ macroChildren.push({
+ type: "element" as const,
+ tagName: "ac:plain-text-body",
+ properties: {},
+ children: [
+ {
+ type: "text" as const,
+ value: ``,
+ },
+ ],
+ });
+
+ return {
+ type: "element" as const,
+ tagName: "ac:structured-macro",
+ properties: {
+ "ac:name": "code",
+ },
+ children: macroChildren,
+ };
+ });
+ };
+ };
+
+/**
+ * Extract the language from the code element's className property.
+ * Markdown renderers typically add classes like "language-javascript"
+ * to code elements.
+ *
+ * @param codeElement - The code element to extract the language from
+ * @returns The language identifier or undefined if not found
+ */
+function extractLanguage(codeElement: HastElement): string | undefined {
+ const className = codeElement.properties?.className;
+
+ if (!className) {
+ return undefined;
+ }
+
+ // className can be a string or an array of strings
+ const classNames = Array.isArray(className) ? className : [className];
+
+ // Look for a class that starts with "language-"
+ for (const cls of classNames) {
+ if (typeof cls === "string" && cls.startsWith("language-")) {
+ return cls.substring(9); // Remove "language-" prefix
+ }
+ }
+
+ return undefined;
+}
+
+/**
+ * Extract all text content from an element recursively.
+ *
+ * @param element - The element to extract text from
+ * @returns The concatenated text content
+ */
+function extractTextContent(element: HastElement): string {
+ let text = "";
+
+ for (const child of element.children) {
+ if (child.type === "text") {
+ text += (child as HastText).value;
+ } else if (child.type === "element") {
+ text += extractTextContent(child as HastElement);
+ }
+ }
+
+ return text;
+}
+
+export default rehypeReplaceCodeBlocks;
diff --git a/components/markdown-confluence-sync/test/unit/specs/confluence/transformer/support/rehype/rehype-replace-code-blocks.test.ts b/components/markdown-confluence-sync/test/unit/specs/confluence/transformer/support/rehype/rehype-replace-code-blocks.test.ts
new file mode 100644
index 00000000..7e72e50a
--- /dev/null
+++ b/components/markdown-confluence-sync/test/unit/specs/confluence/transformer/support/rehype/rehype-replace-code-blocks.test.ts
@@ -0,0 +1,228 @@
+// SPDX-FileCopyrightText: 2025 Telefónica Innovación Digital
+// SPDX-License-Identifier: Apache-2.0
+
+import rehypeParse from "rehype-parse";
+import rehypeRaw from "rehype-raw";
+import rehypeStringify from "rehype-stringify";
+import { unified } from "unified";
+
+import rehypeReplaceCodeBlocks from "@src/lib/confluence/transformer/support/rehype/rehype-replace-code-blocks";
+
+describe("rehype-replace-code-blocks", () => {
+ it("should be defined", () => {
+ expect(rehypeReplaceCodeBlocks).toBeDefined();
+ });
+
+ it("should replace code block with language to Confluence code macro", () => {
+ // Arrange
+ const html =
+ 'const x = 42;
';
+
+ // Act
+ const result = unified()
+ .use(rehypeParse)
+ .use(rehypeRaw)
+ .use(rehypeReplaceCodeBlocks)
+ .use(rehypeStringify, {
+ allowDangerousHtml: true,
+ closeSelfClosing: true,
+ tightSelfClosing: true,
+ })
+ .processSync(html)
+ .toString();
+
+ // Assert
+ expect(result).toContain('');
+ expect(result).toContain('');
+ expect(result).toContain("javascript");
+ expect(result).toContain("");
+ expect(result).toContain("<![CDATA[const x = 42;]]>");
+ });
+
+ it("should replace code block without language to Confluence code macro", () => {
+ // Arrange
+ const html = "plain text code
";
+
+ // Act
+ const result = unified()
+ .use(rehypeParse)
+ .use(rehypeRaw)
+ .use(rehypeReplaceCodeBlocks)
+ .use(rehypeStringify, {
+ allowDangerousHtml: true,
+ closeSelfClosing: true,
+ tightSelfClosing: true,
+ })
+ .processSync(html)
+ .toString();
+
+ // Assert
+ expect(result).toContain('');
+ expect(result).not.toContain('');
+ expect(result).toContain("");
+ expect(result).toContain("<![CDATA[plain text code]]>");
+ });
+
+ it("should handle different programming languages", () => {
+ // Arrange
+ const languages = ["python", "java", "typescript", "bash", "sql"];
+
+ for (const lang of languages) {
+ const html = `code here
`;
+
+ // Act
+ const result = unified()
+ .use(rehypeParse)
+ .use(rehypeStringify)
+ .use(rehypeReplaceCodeBlocks)
+ .processSync(html)
+ .toString();
+
+ // Assert
+ expect(result).toContain('');
+ expect(result).toContain(`${lang}`);
+ expect(result).toContain("<![CDATA[code here]]>");
+ }
+ });
+
+ it("should handle code with special characters", () => {
+ // Arrange
+ const html =
+ 'const str = "Hello & \'Friends\'";
';
+
+ // Act
+ const result = unified()
+ .use(rehypeParse)
+ .use(rehypeRaw)
+ .use(rehypeReplaceCodeBlocks)
+ .use(rehypeStringify, {
+ allowDangerousHtml: true,
+ closeSelfClosing: true,
+ tightSelfClosing: true,
+ })
+ .processSync(html)
+ .toString();
+
+ // Assert
+ expect(result).toContain('');
+ expect(result).toContain(
+ "<![CDATA[const str = \"Hello & 'Friends'\";]]>",
+ );
+ });
+
+ it("should handle multi-line code blocks", () => {
+ // Arrange
+ const html = `function test() {
+ return true;
+}
`;
+
+ // Act
+ const result = unified()
+ .use(rehypeParse)
+ .use(rehypeRaw)
+ .use(rehypeReplaceCodeBlocks)
+ .use(rehypeStringify, {
+ allowDangerousHtml: true,
+ closeSelfClosing: true,
+ tightSelfClosing: true,
+ })
+ .processSync(html)
+ .toString();
+
+ // Assert
+ expect(result).toContain('');
+ expect(result).toContain("function test()");
+ expect(result).toContain("return true;");
+ });
+
+ it("should not transform pre elements without code children", () => {
+ // Arrange
+ const html = "just text
";
+
+ // Act
+ const result = unified()
+ .use(rehypeParse)
+ .use(rehypeRaw)
+ .use(rehypeReplaceCodeBlocks)
+ .use(rehypeStringify, {
+ allowDangerousHtml: true,
+ closeSelfClosing: true,
+ tightSelfClosing: true,
+ })
+ .processSync(html)
+ .toString();
+
+ // Assert
+ expect(result).not.toContain('');
+ expect(result).toContain("just text
");
+ });
+
+ it("should not transform other elements", () => {
+ // Arrange
+ const html = "paragraph
division";
+
+ // Act
+ const result = unified()
+ .use(rehypeParse)
+ .use(rehypeRaw)
+ .use(rehypeReplaceCodeBlocks)
+ .use(rehypeStringify, {
+ allowDangerousHtml: true,
+ closeSelfClosing: true,
+ tightSelfClosing: true,
+ })
+ .processSync(html)
+ .toString();
+
+ // Assert
+ expect(result).not.toContain('');
+ expect(result).toContain("paragraph
");
+ expect(result).toContain("division");
+ });
+
+ it("should handle code blocks with empty content", () => {
+ // Arrange
+ const html = '
';
+
+ // Act
+ const result = unified()
+ .use(rehypeParse)
+ .use(rehypeRaw)
+ .use(rehypeReplaceCodeBlocks)
+ .use(rehypeStringify, {
+ allowDangerousHtml: true,
+ closeSelfClosing: true,
+ tightSelfClosing: true,
+ })
+ .processSync(html)
+ .toString();
+
+ // Assert
+ expect(result).toContain('');
+ expect(result).toContain("<![CDATA[]]>");
+ });
+
+ it("should handle code blocks with multiple class names", () => {
+ // Arrange
+ const html =
+ 'const x = 42;
';
+
+ // Act
+ const result = unified()
+ .use(rehypeParse)
+ .use(rehypeRaw)
+ .use(rehypeReplaceCodeBlocks)
+ .use(rehypeStringify, {
+ allowDangerousHtml: true,
+ closeSelfClosing: true,
+ tightSelfClosing: true,
+ })
+ .processSync(html)
+ .toString();
+
+ // Assert
+ expect(result).toContain('');
+ expect(result).toContain('typescript');
+ expect(result).toContain("<![CDATA[const x = 42;]]>");
+ });
+});