/* * Copyright 2013, Stephan Aßmus . * All rights reserved. Distributed under the terms of the MIT License. */ #include "MarkupParser.h" #include #include #include MarkupParser::MarkupParser() : fNormalStyle(), fBoldStyle(), fItalicStyle(), fBoldItalicStyle(), fHeadingStyle(), fParagraphStyle(), fHeadingParagraphStyle(), fBulletStyle(), fCurrentCharacterStyle(&fNormalStyle), fCurrentParagraphStyle(&fParagraphStyle), fSpanStartOffset(0) { _InitStyles(); } MarkupParser::MarkupParser(const CharacterStyle& characterStyle, const ParagraphStyle& paragraphStyle) : fNormalStyle(characterStyle), fBoldStyle(), fItalicStyle(), fBoldItalicStyle(), fHeadingStyle(), fParagraphStyle(paragraphStyle), fHeadingParagraphStyle(), fBulletStyle(), fCurrentCharacterStyle(&fNormalStyle), fCurrentParagraphStyle(&fParagraphStyle), fSpanStartOffset(0) { _InitStyles(); } void MarkupParser::SetStyles(const CharacterStyle& characterStyle, const ParagraphStyle& paragraphStyle) { fNormalStyle = characterStyle; fParagraphStyle = paragraphStyle; _InitStyles(); } TextDocumentRef MarkupParser::CreateDocumentFromMarkup(const BString& text) { TextDocumentRef document(new(std::nothrow) TextDocument(), true); if (!document.IsSet()) return document; AppendMarkup(document, text); return document; } void MarkupParser::AppendMarkup(const TextDocumentRef& document, const BString& text) { fTextDocument.SetTo(document); fCurrentCharacterStyle = &fNormalStyle; fCurrentParagraphStyle = &fParagraphStyle; fCurrentParagraph = Paragraph(*fCurrentParagraphStyle); fSpanStartOffset = 0; _ParseText(text); fTextDocument.Unset(); } // #pragma mark - private void MarkupParser::_InitStyles() { fBoldStyle = fNormalStyle; fBoldStyle.SetBold(true); fItalicStyle = fNormalStyle; fItalicStyle.SetItalic(true); fBoldItalicStyle = fNormalStyle; fBoldItalicStyle.SetBold(true); fBoldItalicStyle.SetItalic(true); float fontSize = fNormalStyle.Font().Size(); fHeadingStyle = fNormalStyle; fHeadingStyle.SetFontSize(ceilf(fontSize * 1.15f)); fHeadingStyle.SetBold(true); fHeadingParagraphStyle = fParagraphStyle; fHeadingParagraphStyle.SetSpacingTop(ceilf(fontSize * 0.8f)); fHeadingParagraphStyle.SetSpacingBottom(ceilf(fontSize * 0.5f)); fHeadingParagraphStyle.SetJustify(false); fBulletStyle = fParagraphStyle; fBulletStyle.SetBullet(Bullet("•", fontSize)); fBulletStyle.SetLineInset(ceilf(fontSize * 0.8f)); } void MarkupParser::_ParseText(const BString& text) { int32 start = 0; int32 offset = 0; int32 charCount = text.CountChars(); const char* c = text.String(); while (offset <= charCount) { uint32 nextChar = UTF8ToCharCode(&c); switch (nextChar) { // Requires two line-breaks to start a new paragraph, unles the current // paragraph is already considered a bullet list item. Doesn't work well // with current set of packages. // case '\n': // _CopySpan(text, start, offset); // if (offset + 1 < charCount && c[0] == '\n') { // _FinishParagraph(); // offset += 1; // c += 1; // } else if (fCurrentParagraph.Style() == fBulletStyle) { // _FinishParagraph(); // } // start = offset + 1; // break; case '\n': _CopySpan(text, start, offset); if (offset > 0 && c[-1] != ' ') _FinishParagraph(offset >= charCount); start = offset + 1; break; case '\0': _CopySpan(text, start, offset); _FinishParagraph(true); start = offset + 1; break; case '\'': if (offset + 2 < charCount && c[0] == '\'') { int32 tickCount = 2; if (c[1] == '\'') tickCount = 3; // Copy previous span using current style, excluding the // ticks. _CopySpan(text, start, offset); if (tickCount == 2) _ToggleStyle(fItalicStyle); else if (tickCount == 3) _ToggleStyle(fBoldStyle); // Don't include the ticks in the next span. offset += tickCount - 1; start = offset + 1; c += tickCount - 1; } break; case '=': // Detect headings if (offset == start && fCurrentParagraph.IsEmpty() && offset + 2 < charCount && c[0] == '=' && c[1] == ' ') { fCurrentParagraph.SetStyle(fHeadingParagraphStyle); fCurrentCharacterStyle = &fHeadingStyle; offset += 2; c += 2; start = offset + 1; } else if (offset > start && offset + 2 < charCount && c[0] == '=' && c[1] == '\n') { _CopySpan(text, start, offset - 1); offset += 2; c += 2; _FinishParagraph(offset >= charCount); start = offset + 1; } break; case ' ': // Detect bullets at line starts (preceeding space) if (offset == start && fCurrentParagraph.IsEmpty() && offset + 2 < charCount && (c[0] == '*' || c[0] == '-') && c[1] == ' ') { fCurrentParagraph.SetStyle(fBulletStyle); offset += 2; c += 2; start = offset + 1; } break; case '*': case '-': // Detect bullets at line starts (no preceeding space) if (offset == start && fCurrentParagraph.IsEmpty() && offset + 1 < charCount && c[0] == ' ') { fCurrentParagraph.SetStyle(fBulletStyle); offset += 1; c += 1; start = offset + 1; } break; default: break; } offset++; } } void MarkupParser::_CopySpan(const BString& text, int32& start, int32 end) { if (start >= end) return; BString subString; text.CopyCharsInto(subString, start, end - start); fCurrentParagraph.Append(TextSpan(subString, *fCurrentCharacterStyle)); start = end; } void MarkupParser::_ToggleStyle(const CharacterStyle& style) { if (fCurrentCharacterStyle == &style) fCurrentCharacterStyle = &fNormalStyle; else fCurrentCharacterStyle = &style; } void MarkupParser::_FinishParagraph(bool isLast) { if (!isLast) fCurrentParagraph.Append(TextSpan("\n", *fCurrentCharacterStyle)); if (fCurrentParagraph.IsEmpty()) { // Append empty span fCurrentParagraph.Append(TextSpan("", fNormalStyle)); } fTextDocument->Append(fCurrentParagraph); fCurrentParagraph.Clear(); fCurrentParagraph.SetStyle(fParagraphStyle); fCurrentCharacterStyle = &fNormalStyle; }