001/*
002* Copyright 2025 - 2025 the original author or authors.
003*
004* Licensed under the Apache License, Version 2.0 (the "License");
005* you may not use this file except in compliance with the License.
006* You may obtain a copy of the License at
007*
008* https://www.apache.org/licenses/LICENSE-2.0
009*
010* Unless required by applicable law or agreed to in writing, software
011* distributed under the License is distributed on an "AS IS" BASIS,
012* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013* See the License for the specific language governing permissions and
014* limitations under the License.
015*/
016package com.agentsflex.skill.util;
017
018import java.util.HashMap;
019import java.util.Map;
020
021/**
022 * Parser for Markdown documents with optional YAML front matter.
023 * <p>
024 * This parser extracts YAML front matter (metadata) and content from Markdown documents.
025 * Front matter is delimited by triple dashes (---) at the beginning of the document and
026 * contains key-value pairs in YAML format.
027 * <p>
028 * Example Markdown with front matter:
029 *
030 * <pre>{@code
031 * ---
032 * title: My Document
033 * author: John Doe
034 * date: 2024-01-15
035 * ---
036 *
037 * # Heading
038 *
039 * Document content here.
040 * }</pre>
041 * <p>
042 * The parser supports:
043 * <ul>
044 * <li>Front matter with key-value pairs separated by colons</li>
045 * <li>Values with or without quotes (both single and double quotes are supported)</li>
046 * <li>Documents without front matter (entire content is treated as body)</li>
047 * <li>Empty or null markdown input</li>
048 * </ul>
049 *
050 * @author Christian Tzolov
051 */
052public class MarkdownParser {
053
054        /**
055         * Map containing the parsed front matter key-value pairs.
056         */
057        private Map<String, Object> frontMatter;
058
059        /**
060         * The content of the markdown document (everything after the front matter).
061         */
062        private String content;
063
064        /**
065         * Constructs a new MarkdownParser and parses the provided markdown content. Parses
066         * the markdown content to extract front matter and body content.
067         * <p>
068         * Front matter must start with "---" at the beginning of the document and end with
069         * another "---". Everything between these delimiters is parsed as front matter.
070         * Everything after the closing delimiter is treated as content.
071         * @param markdown the markdown string to parse, may contain front matter delimited by
072         * triple dashes (---). Can be null or empty.
073         */
074        public MarkdownParser(String markdown) {
075
076                frontMatter = new HashMap<>();
077                content = "";
078
079                if (markdown == null || markdown.isEmpty()) {
080                        return;
081                }
082
083                // Check if document starts with front-matter delimiter (---)
084                if (markdown.startsWith("---")) {
085                        // Find the closing delimiter
086                        int endIndex = markdown.indexOf("---", 3);
087
088                        if (endIndex != -1) {
089                                // Extract front-matter section
090                                String frontMatterSection = markdown.substring(3, endIndex).trim();
091                                parseFrontMatter(frontMatterSection);
092
093                                // Extract remaining content (skip the closing --- and any following
094                                // newlines)
095                                content = markdown.substring(endIndex + 3).trim();
096                        }
097                        else {
098                                // No closing delimiter found, treat entire document as content
099                                content = markdown;
100                        }
101                }
102                else {
103                        // No front-matter, entire document is content
104                        content = markdown;
105                }
106
107        }
108
109        private void parseFrontMatter(String frontMatterSection) {
110                String[] lines = frontMatterSection.split("\n");
111
112                for (String line : lines) {
113                        line = line.trim();
114
115                        if (line.isEmpty()) {
116                                continue;
117                        }
118
119                        // Split on first colon
120                        int colonIndex = line.indexOf(':');
121                        if (colonIndex > 0) {
122                                String key = line.substring(0, colonIndex).trim();
123                                String value = line.substring(colonIndex + 1).trim();
124
125                                // Removes surrounding quotes from a value string if present.
126                                value = removeQuotes(value);
127
128                                frontMatter.put(key, value);
129                        }
130                }
131        }
132
133        private String removeQuotes(String value) {
134                if (value.length() >= 2) {
135                        if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
136                                return value.substring(1, value.length() - 1);
137                        }
138                }
139                return value;
140        }
141
142        /**
143         * Returns a copy of the parsed front matter as a map.
144         * <p>
145         * The returned map contains all key-value pairs extracted from the front matter
146         * section. If no front matter was present or the input was null/empty, returns an
147         * empty map.
148         * @return a new map containing the front matter key-value pairs
149         */
150        public Map<String, Object> getFrontMatter() {
151                return new HashMap<>(frontMatter);
152        }
153
154        /**
155         * Returns the content portion of the markdown document.
156         * <p>
157         * This is everything after the closing front matter delimiter (---), with leading and
158         * trailing whitespace trimmed. If no front matter was present, returns the entire
159         * document. If the input was null or empty, returns an empty string.
160         * @return the markdown content as a string
161         */
162        public String getContent() {
163                return content;
164        }
165}