{"id":14991043,"url":"https://github.com/move-agency/markymark-android","last_synced_at":"2025-04-12T03:25:03.667Z","repository":{"id":30295674,"uuid":"124518729","full_name":"Move-Agency/MarkyMark-Android","owner":"Move-Agency","description":"Markdown parser for Android","archived":false,"fork":false,"pushed_at":"2025-02-28T14:02:39.000Z","size":2359,"stargazers_count":30,"open_issues_count":8,"forks_count":6,"subscribers_count":6,"default_branch":"develop","last_synced_at":"2025-03-25T23:01:51.684Z","etag":null,"topics":["android","contentful","markdown","markdown-parser"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Move-Agency.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-09T09:27:56.000Z","updated_at":"2025-01-27T09:03:02.000Z","dependencies_parsed_at":"2024-05-22T15:46:35.581Z","dependency_job_id":"80792c7d-61d5-4792-aa5b-bbdc981843ca","html_url":"https://github.com/Move-Agency/MarkyMark-Android","commit_stats":{"total_commits":48,"total_committers":9,"mean_commits":5.333333333333333,"dds":0.7708333333333334,"last_synced_commit":"b03fbff25d91fcd494a992bbbf8439d9beb4cb64"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Move-Agency%2FMarkyMark-Android","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Move-Agency%2FMarkyMark-Android/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Move-Agency%2FMarkyMark-Android/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Move-Agency%2FMarkyMark-Android/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Move-Agency","download_url":"https://codeload.github.com/Move-Agency/MarkyMark-Android/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248511130,"owners_count":21116357,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","contentful","markdown","markdown-parser"],"created_at":"2024-09-24T14:21:22.192Z","updated_at":"2025-04-12T03:25:03.643Z","avatar_url":"https://github.com/Move-Agency.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![](https://jitpack.io/v/M2Mobi/MarkyMark-Android.svg)](https://jitpack.io/#M2Mobi/MarkyMark-Android)\n\n# MarkyMark\n\nMarkyMark is a parser that converts markdown into native views. The way it looks is highly customizable and the supported markdown syntax is easy to extend.\n\n## Usage Android\n\nCreate an Android instance of MarkyMark that can convert Markdown to View's. A helper class is provided to create an Android instance.\n\n```kotlin\nval markyMark = MarkyMarkAndroid.getMarkyMark(this, ContentfulFlavor(), PicassoImageLoader())\n```\n\nThere are 2 ways to use the MarkyMark instance.\n\nYou can use the MarkyMark instance to parse the Markdown into a list of View's and add them to (for example) a LinearLayout:\n```kotlin\nval linearLayout = findViewById\u003cLinearLayout\u003e(R.id.linearlayout)\nval views = markyMark.parseMarkdown(\"# Header\\nParagraph etc\")\nfor (view in views) {\n     layout.addView(view)\n}\n```\n\nOr use the provided [MarkyMarkView](markymark-android/src/main/java/com/m2mobi/markymarkandroid/MarkyMarkView.java) to do it for you.\n```kotlin\nval markyMarkView = findViewById\u003cMarkyMarkView\u003e(R.id.markymarkview)\nmarkyMarkView.setMarkyMark(markyMark)\nmarkyMarkView.setMarkdown(\"# Header\\nParagraph etc\")\n```\n\n## Styling\n\nTo style your Markdown content you can override MarkyMark styles where necessary.\n\n```xml\n\u003c!-- Base application theme. --\u003e\n\u003cstyle name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\"\u003e\n    \u003c!-- Customize your theme here. --\u003e\n    \u003citem name=\"colorPrimary\"\u003e@color/colorPrimary\u003c/item\u003e\n    \u003citem name=\"colorPrimaryDark\"\u003e@color/colorPrimaryDark\u003c/item\u003e\n    \u003citem name=\"colorAccent\"\u003e@color/colorAccent\u003c/item\u003e\n\n    \u003citem name=\"MarkyMarkTheme\"\u003e@style/MarkdownStyle\u003c/item\u003e  \n \u003c/style\u003e\n    \n \u003c!-- Theme used by MarkyMark --\u003e\n \u003cstyle name=\"MarkdownStyle\" parent=\"MarkyMarkStyle\"\u003e\n    \u003citem name=\"android:lineSpacingExtra\"\u003e4dp\u003c/item\u003e\n    \u003citem name=\"android:lineSpacingMultiplier\"\u003e1\u003c/item\u003e\n    \u003citem name=\"MarkdownHeader4Style\"\u003e@style/Header4\u003c/item\u003e\n\u003c/style\u003e\n\n\u003c!-- Different color for H4 tags --\u003e\n\u003cstyle name=\"Header4\" parent=\"MarkyMarkHeader4\"\u003e\n    \u003citem name=\"android:textColor\"\u003e#52c222\u003c/item\u003e\n\u003c/style\u003e\n```\n\n### Available parent styles\n\n- `MarkyMarkCode`\n- `MarkyMarkHeader`\n- `MarkyMarkHeader1`\n- `MarkyMarkHeader2`\n- `MarkyMarkHeader3`\n- `MarkyMarkHeader4`\n- `MarkyMarkHeader5`\n- `MarkyMarkHeader6`\n- `MarkyMarkHeaderHorizontalRule` // horizontal lines\n- `MarkyMarkImage`\n- `MarkyMarkList`\n- `MarkyMarkParagraph`\n- `MarkyMarkQuote`\n\nIf you want more customization it is also possible to provide your own custom inline/block items, or replace existing ones. More on how you can use these in **Advanced Usage** found below\n\n```kotlin\nval themedContext = ThemedContext(activity)\n\n// These are some of the default ones, but you get the point\nval displayItems = mutableListOf\u003cDisplayItem\u003c*,*,*\u003e\u003e().apply {\n\tadd(HeaderDisplayItem(themedContext))\n\tadd(ParagraphDisplayItem(themedContext))\n}\nval inlineDisplayItems = mutableListOf\u003cInlineDisplayItem\u003c*,*\u003e\u003e().apply {\n\tadd(BoldInlineDisplayItem())\n}\nval markyMark = MarkyMarkAndroid.getMarkyMark(\n                activity,\n                ContentfulFlavor(),\n                displayItems,\n                inlineDisplayItems,\n                PicassoImageLoader()\n)\n```\n\n### Loading images\n\nYou can use your favorite image loading library to load images. When creating a MarkyMark android instance an implementation of [ImageLoader](markymark-android/src/main/java/com/m2mobi/markymarkandroid/ImageLoader.java) needs to be provided, an example using [Picasso](https://github.com/square/picasso) can be found [here](markymark-sample/src/main/java/com/m2mobi/markymark/PicassoImageLoader.kt).\n\n# Advanced usage\n---\n\n## Adding your own rules\n\nAs of now only a `ContentfulFlavor` is included, however it is entirely possible to create your own Markdown flavor\nand/or corresponding rules.\n\nAdding a rule requires these steps\n\n### Extend the marker interface `MarkdownItem`\n\nCreate a `MarkdownItem` from which you can create a `View` later, so you'll want to have every piece of information needed in order to create said `View`\n\n```kotlin\ndata class NewMarkdownItem(val content: String) : MarkdownItem\n```\n\n### Extend `DisplayItem\u003cView, NewMarkdownItem, Spanned\u003e`\n\nCreate a `DisplayItem` that can handle your `NewMarkdownItem` and convert it into a `View`\n\n```kotlin\nclass NewDisplayItem(val context: Context) : DisplayItem\u003cView, NewMarkdownItem, Spanned\u003e {\n\n\toverride fun create(markdownItem: NewMarkdownItem, inlineConverter: InlineConverter\u003cSpanned\u003e) : View {\n\t\treturn TextView(context).apply {\n\t\t\ttext = inlineConverter.convert(markdownItem.content)\n\t\t}\n\t}\n}\n```\n\nNow add this item to your `ViewConverter` before you initialize `MarkyMark` like shown at the top of this `README`\n\n```java\nviewConverter.addMapping(NewDisplayItem(themedContext))\n```\n\n### Extend `Rule`\n\nCreate a `Rule` that recognizes your new item and creates a corresponding `MarkdownItem` for it.\nMost new rules will just be single line, like headers, in that case your new rule can just extend `RegexRule`.\nReturn your regular expression `Pattern` in the `getRegex()` method and return your new `MarkdownItem` in the `toMarkdownItem(markdownLines: List\u003cString\u003e)`\n\n```kotlin\nclass NewRule : RegexRule {\n\n\toverride fun getRegex() : Pattern = Pattern.compile(\"some regex\")\n\t\n\toverride fun toMarkdownItem(markdownLines: List\u003cString\u003e) : MarkdownItem {\n\t    // In this case, since it is a single line rule\n\t    // markdownLines will always be an list with one String\n\t    return NewMarkdownItem(markdownLines.first())\n\t}\n}\n```\n\n### Adding the new rule to `MarkyMark`\n\nYou can add a new rule to your `MarkyMark` instance like this\n\n```kotlin\n// adding rule to MarkyMark instance\nmarkyMark.addRule(NewRule())\n```\n\nOr create a new `Flavor` altogether\n\n```kotlin\nclass OtherFlavor : Flavor {\n\n\toverride fun getRules() : List\u003cRule\u003e {\n\t\treturn mutableListOf\u003cRule\u003e().apply {\n\t\t\t// add all the rules\n\t\t}\n\t}\n\n\toverride fun getInlineRules() : List\u003cInlineRule\u003e {\n\t\treturn mutableListOf\u003cInlineRule\u003e().apply {\n\t\t\t// Add all the rules\n\t\t}\n\t}\n\n\toverride fun getDefaultRule() = NewDefaultRule()\n}\n```\n\nAnd pass it in the `MarkyMark.Builder()`\n\n```kotlin\nMarkyMark.Builder\u003cView\u003e().addFlavor(OtherFlavor()) // etc\n```\n\n### Multi line blocks\n\nFor more complicated `Rules` that can detect multi-line blocks you'll want to extend `Rule` and you have override the `conforms(final List\u003cString\u003e pMarkdownLines)` method where you would return `true` if the **first** line is recognized as the start of your block, and false otherwise. However there is a catch, you have to set a global integer with the amount of lines there are in this block, which you have to return in the `getLinesConsumed()` method. This means that you have to count the amount of lines that belong to your block inside the `conforms()` method, which isn't desirable and should be refactored as soon as possible.\n\n```kotlin\nclass NewRule : Rule {\n\n    /** Start pattern of the block */\n    private val startPattern = Pattern.compile(\"some regex\")\n    \n    /** End pattern of the block */\n    private val endPattern = Pattern.compile(\"some regex\")\n    \n    /** The amount of lines of the block */\n    private var lines : Int = 0\n    \n    override fun getLinesConsumed(): Int = lines\n\n    override fun conforms(markdownLines: MutableList\u003cString\u003e): Boolean {\n        if (!startPattern.matcher(markdownLines.first()).matches()) {\n            return false\n        }\n        lines = 0\n        for (line in markdownLines) {\n            lines += 1\n            if (endPattern.matcher(line).matches()) {\n                return true\n            }\n        }\n        return false\n    }\n\n    override fun toMarkdownItem(markdownLines: MutableList\u003cString\u003e): MarkdownItem = SomeMarkdownItem()\n}\n```\n\n### Inline rules\n\nFor detecting inline Markdown, like **bold** or *italic* strings, instead of extending `RegexRule` or `Rule` just extend `InlineRule` and return a `MarkdownString` instead of a `MarkdownItem`.\n\nFor example, a rule that would match %%some text%% would look like this\n\n```kotlin\nclass PercentRule : InlineRule {\n\n    override fun getRegex() : Pattern = Pattern.compile(\"%{2}(.+?)-{2}\")\n\n    override fun toMarkdownString(content: String) = PercentString(content, true)\n}\n```\n\nWhere `PercentString` would be an extension of `MarkdownString`\n\n```kotlin\nclass PercentString(content: String, canHasChildItems: Boolean) : MarkdownString(content, canHasChildItems)\n```\n\nFor inline Markdown, instead of extending `DisplayItem\u003cView, Foo, Spanned\u003e` you'd want to extend `InlineDisplayItem\u003cSpanned, PercentString\u003e`\n\n```kotlin\nclass PercentInlineDisplayItem : InlineDisplayItem\u003cSpanned, PercentString\u003e {\n\n    override fun create(inlineConverter: InlineConverter\u003cSpanned\u003e, markdownString: PercentString): Spanned {\n        // return your Spannable String here\n    }\n}\n```\n\nAnd add them to `MarkyMark`s `InlineConverter\u003cSpanned\u003e` as explained above\n\n```kotlin\ninlineViewConverter.addMapping(PercentInlineDisplayItem())\n```\n\n\n## Supported tags in Contentful Flavour\n\n```\n# Headers\n---\n\n# H1\n## H2\n### H3\n#### H4\n##### H5\n###### H6\n### __*bold \u0026 italic*__\n\n# Lists\n---\n\n## Ordered list\n1. Number 1\n2. Number 2\n3. Number 3\n5. Number 4\n  1. Nested 1\n  2. Nested 2\n6. Number 5 click [here](https://m2mobi.com)\n7. Number 5\n\n## Unordered list\n- Item 1\n- Item 2\n- Item 3\n  - Nested item 1\n  - Nested item 2\n    - Sub-nested item 1\n    - Sub-nested item 2\n      - Sub-sub-nested item 1\n      - Sub-sub-nested item 2\n       - Sub-sub-nested item 3 (with single space) and a very long piece of text\n\n## Combo\n\n1. Ordered 1\n2. Ordered 2\n- Unordered 1\n- Unordered 2\n  - __*Nested unordered 1 bold*__\n  - Nested unordered 2\n    1. Sub-nested ordered 1\n    2. Sub-nested ordered 2\n    - unordered\n    - unordered\n\n# Paragraphs\n\n---\n## Quotes\n\u003e Markdown is *awesome*\n\u003e Seriously..\n\n## Links\n[This is a test link](https://m2mobi.com)\nInline links are also possible, click [here](https://m2mobi.com)\nPhone numbers as well [+06-12345678](tel:06-12345678)\n\n## Code\n\n`inline code`\n```code block```\n\n## Styled text\nThis is __bold__, this is *italic*, this is ~~striked out~~, this is everything __~~*combined*~~__.\nSpecial html symbols: `\u0026euro; \u0026copy;` become -\u003e \u0026euro; \u0026copy;\n\n## Images\n---\n\n`![Alternate text](www.imageurl.com)`\n\n```\n\n# Download\n---\n\nAdd the [Jitpack.io](www.jitpack.io) repository to your project root `build.gradle` file\n\n```groovy\nallprojects {\n    repositories {\n        maven { url \"https://jitpack.io\" }\n    }\n}\n```\n\nAndroid MarkyMark with Contentful support\n```groovy\ncompile 'com.github.m2mobi.MarkyMark-Android:markymark-android:0.2.2' \ncompile 'com.github.m2mobi.MarkyMark-Android:markymark-contentful:0.2.2' \n```\n\nIf you want to use MarkyMark outside of a Android project you might be interested in these pure Java modules\n\n```groovy\n// Base\ncompile 'com.github.m2mobi.MarkyMark-Android:markymark-core:0.2.2' \n// Commons\ncompile 'com.github.m2mobi.MarkyMark-Android:markymark-commons:0.2.2' \n```\n\nFrom which you can create `MarkyMark` like so\n\n```kotlin\nval viewConverter = Converter\u003cView\u003e().apply {\n\taddMapping(SomeItem())\n}\nval inlineConverter = InlineConverter\u003cSpanned\u003e().apply {\n        addMapping(SomeInlineItem())\n}\n\nval markyMark = MarkyMark.Builder\u003cView\u003e()\n\t.addFlavor(SomeFlavor())\n\t.setConverter(viewConverter)\n\t.setInlineConverter(inlineConverter)\n\t.build()\n```\n\n# Contributions\n---\n\nContributions are encouraged! Create a PR against the Development branch and always run the tests before doing so.\nAs of now only the `markymark-contentful` module has tests.\n\n## Starting points for contributions\n\n### Rule parsing\n\nLike mentioned in **Advanced Usage**, the way rules are implemented now is:\n- Pass a list of `String`s to an rule\n- The rule checks whether the first string conforms to that particular `MarkdownItem`\n- The rule also counts (if the first `String` conforms) how many lines belong to the item\n- The parser asks the rule how many lines belong to the item\n- The parser passes those lines to the rule to create the `MarkdownItem`\n- The parser removes those lines from the original list of `Strings`\n- Repeat\n\nLike previously mentioned, the rule counts the amount of lines needed for its item inside the `conforms()` method, something that is \npretty unexpected when you aren't familiar with the code base. This process needs refactoring.\n  \n### Table support\n\nImplement support for tables.\n\n# Author\n---\n\nM2mobi, info@m2mobi.com\n\n# License\n---\n\n```\nThe MIT License (MIT)\n\nCopyright (c) 2016-2020 M2mobi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmove-agency%2Fmarkymark-android","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmove-agency%2Fmarkymark-android","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmove-agency%2Fmarkymark-android/lists"}