Получить из текста формата подобному markdown - список

Рейтинг: 0Ответов: 1Опубликовано: 15.01.2023

Я хочу получить некоторые возможности markdown разметки, какой-то базовый функционал. На данный момент это: выделение текста жирным, курсивом, курсивом и жирный, список. Сейчас я хочу понять как мне реализовать парсинг списка и возможно ли обойтись обычным регулярным выражением?

var isRegExp = function (re) { 
  return re instanceof RegExp;
};
var escapeRegExp = function escapeRegExp(string) {
  var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
    reHasRegExpChar = RegExp(reRegExpChar.source);

  return (string && reHasRegExpChar.test(string))
    ? string.replace(reRegExpChar, '\\$&')
    : string;
};
var isString = function (value) {
  return typeof value === 'string';
};
var flatten = function (array) {
  var newArray = [];

  array.forEach(function (item) {
    if (Array.isArray(item)) {
      newArray = newArray.concat(item);
    } else {
      newArray.push(item);
    }
  });

  return newArray;
};
function replaceString(str, match, fn) {
  var curCharStart = 0;
  var curCharLen = 0;

  if (str === '') {
    return str;
  } else if (!str || !isString(str)) {
    throw new TypeError('First argument to react-string-replace#replaceString must be a string');
  }

  var re = match;

  if (!isRegExp(re)) {
    re = new RegExp('(' + escapeRegExp(re) + ')', 'gi');
  }

  var result = str.split(re);
  
  for (var i = 1, length = result.length; i < length; i += 2) {
    if (result[i] === undefined || result[i - 1] === undefined) {
      console.warn('reactStringReplace: Encountered undefined value during string replacement. Your RegExp may not be working the way you expect.');
      continue;
    }

    curCharLen = result[i].length;
    curCharStart += result[i - 1].length;
    result[i] = fn(result[i], i, curCharStart);
    curCharStart += curCharLen;
  }

  return result;
}

function reactStringReplace(source, match, fn) {
  if (!Array.isArray(source)) source = [source];

  return flatten(source.map(function(x) {
    return isString(x) ? replaceString(x, match, fn) : x;
  }));
};



const test: string = `
Bold:
-----
I just love **bold text**.
I just love __bold text__.
Love**is**bold

Italic:
-------
Italicized text is the *cat's meow*.
Italicized text is the _cat's meow_.
A*cat*meow

Bold and Italic:
----------------
This text is ***really important***.
This text is ___really important___.
This text is __*really important*__.
This text is **_really important_**.

Test:
-----
***one*** and ***two***

List:
-----
- list item 1
- list item 2
- list item 3
- list item 4
- list item 5
`;

function _wrappedItalic(match: string, index: number): ReactNode
{
    return <em key={index}>{match}</em>;
}

function _wrappedBold(match: string, index: number): ReactNode
{
    return <strong key={index}>{match}</strong>;
}

function _wrappedBoldAndItalic(match: string, index: number): ReactNode
{
    return <em key={index}><strong>{match}</strong></em>;
}

function convertMarkdown(text: string | ReactNodeArray | undefined): ReactNodeArray
{
    // Bold and Italic
    text = reactStringReplace(text, /___(.*?)___/g, _wrappedBoldAndItalic);
    text = reactStringReplace(text, /\*\*\*(.*?)\*\*\*/g, _wrappedBoldAndItalic);
    text = reactStringReplace(text, /__\*(.*?)\*__/g, _wrappedBoldAndItalic);
    text = reactStringReplace(text, /\*\*_(.*?)_\*\*/g, _wrappedBoldAndItalic);

    // Bold
    text = reactStringReplace(text, /\*\*(.*?)\*\*/g, _wrappedBold);
    text = reactStringReplace(text, /__(.*?)__/g, _wrappedBold);

    // Italic
    text = reactStringReplace(text, /\*(.*?)\*/g, _wrappedItalic);
    text = reactStringReplace(text, /_(.*?)_/g, _wrappedItalic);

    // Line break
    text = reactStringReplace(text, /(\n|\\n)/g, (_match, index) =>
    (
        <br key={index} />
    ));

    return text;
}

function App()
{
    return (
        <div>
            {convertMarkdown(test)}
        </div>
    );
}


// Рендеринг
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.0.0/umd/react-dom.production.min.js"></script>

<div id="root"></div>

Ответы

▲ 1

В моем случае такое регулярное выражение оказалось решением:

((?:^-\s+.*\n?)+)(?=\n|$)

var isRegExp = function (re) { 
  return re instanceof RegExp;
};
var escapeRegExp = function escapeRegExp(string) {
  var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
    reHasRegExpChar = RegExp(reRegExpChar.source);

  return (string && reHasRegExpChar.test(string))
    ? string.replace(reRegExpChar, '\\$&')
    : string;
};
var isString = function (value) {
  return typeof value === 'string';
};
var flatten = function (array) {
  var newArray = [];

  array.forEach(function (item) {
    if (Array.isArray(item)) {
      newArray = newArray.concat(item);
    } else {
      newArray.push(item);
    }
  });

  return newArray;
};
function replaceString(str, match, fn) {
  var curCharStart = 0;
  var curCharLen = 0;

  if (str === '') {
    return str;
  } else if (!str || !isString(str)) {
    throw new TypeError('First argument to react-string-replace#replaceString must be a string');
  }

  var re = match;

  if (!isRegExp(re)) {
    re = new RegExp('(' + escapeRegExp(re) + ')', 'gi');
  }

  var result = str.split(re);
  
  for (var i = 1, length = result.length; i < length; i += 2) {
    if (result[i] === undefined || result[i - 1] === undefined) {
      console.warn('reactStringReplace: Encountered undefined value during string replacement. Your RegExp may not be working the way you expect.');
      continue;
    }

    curCharLen = result[i].length;
    curCharStart += result[i - 1].length;
    result[i] = fn(result[i], i, curCharStart);
    curCharStart += curCharLen;
  }

  return result;
}

function reactStringReplace(source, match, fn) {
  if (!Array.isArray(source)) source = [source];

  return flatten(source.map(function(x) {
    return isString(x) ? replaceString(x, match, fn) : x;
  }));
};



const test: string = `
Bold:
-----
I just love **bold text**.
I just love __bold text__.
Love**is**bold

Italic:
-------
Italicized text is the *cat's meow*.
Italicized text is the _cat's meow_.
A*cat*meow

Bold and Italic:
----------------
This text is ***really important***.
This text is ___really important___.
This text is __*really important*__.
This text is **_really important_**.

Test:
-----
***one*** and ***two***

List:
-----
- list item 1
- list item 2
- list item 3
- list item 4
- list item 5

- test 1
- test 2

Numberic List:
-----
1. list item 1
1. list item 2
1. list item 3
1. list item 4
1. list item 5
`;

function _wrappedItalic(match: string, index: number): ReactNode
{
    return <em key={index}>{match}</em>;
}

function _wrappedBold(match: string, index: number): ReactNode
{
    return <strong key={index}>{match}</strong>;
}

function _wrappedBoldAndItalic(match: string, index: number): ReactNode
{
    return <em key={index}><strong>{match}</strong></em>;
}

function _wrappedList(match: string, index: number): ReactNode
{
    const items: string[] = match.split(/\n|\\n/g)
        .filter((item) => item.trim() !== "")
        .map((item) => item.replace(/^-\s+/, "").trim());

    return (
        <ul key={index}>
            {items.map((item, index) => <li key={index}>{item}</li>)}
        </ul>
    );
}

function _wrappedOrderedList(match: string, index: number): ReactNode
{
    const items: string[] = match.split(/\n|\\n/g)
        .filter((item) => item.trim() !== "")
        .map((item) => item.replace(/^\d+\.\s+/, "").trim());

    return (
        <ol key={index}>
            {items.map((item, index) => <li key={index}>{item}</li>)}
        </ol>
    );
}

function convertMarkdown(text: string | ReactNodeArray | undefined): ReactNodeArray
{
    // Bold and Italic
    text = reactStringReplace(text, /___(.*?)___/g, _wrappedBoldAndItalic);
    text = reactStringReplace(text, /\*\*\*(.*?)\*\*\*/g, _wrappedBoldAndItalic);
    text = reactStringReplace(text, /__\*(.*?)\*__/g, _wrappedBoldAndItalic);
    text = reactStringReplace(text, /\*\*_(.*?)_\*\*/g, _wrappedBoldAndItalic);

    // Bold
    text = reactStringReplace(text, /\*\*(.*?)\*\*/g, _wrappedBold);
    text = reactStringReplace(text, /__(.*?)__/g, _wrappedBold);

    // Italic
    text = reactStringReplace(text, /\*(.*?)\*/g, _wrappedItalic);
    text = reactStringReplace(text, /_(.*?)_/g, _wrappedItalic);

    // Ordered Lists
    text = reactStringReplace(text, /((?:^\d+\.\s+.*\n?)+)(?=\n|$)/gm, _wrappedOrderedList);

    // Unordered Lists
    text = reactStringReplace(text, /((?:^-\s+.*\n?)+)(?=\n|$)/gm, _wrappedList);

    // Line break
    text = reactStringReplace(text, /(\n|\\n)/g, (_match, index) =>
    (
        <br key={index} />
    ));

    return text;
}

function App()
{
    return (
        <div>
            {convertMarkdown(test)}
        </div>
    );
}


// Рендеринг
ReactDOM.render(<App/>, document.getElementById('root'));
ul, ol { margin: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.0.0/umd/react-dom.production.min.js"></script>

<div id="root"></div>