如何将带标签的搜索文本解析为有序数组并转换为前端组件?
My Goal
I'm building a search input where users can enter either plain text (e.g., "cats") or tag-based queries like "author:Stan cats year:1990". The end goal is to convert such queries into a component structure like:
<div> <Label type="author" value="Stan"/> <p>cats</p> <Label type="year" value="1990"/> </div>
My Plan
- First, parse the input text (e.g.,
"author:Stan dogs cats year:1990") into an ordered array of objects:
[ { type: "author", value: "stan" }, { type: "text", value: "dogs cats" }, { type: "year", value: "1990" } ]
- Then map over this array to render the correct components:
array.map(({ type, value }) => { if (type === 'text') { return <p>{value}</p> } return <Label type={type} value={value} /> })
Stuck On
I can't figure out how to parse the input into that array while keeping the original order of tags and text. Regex seems like the way to go, but I haven't been able to get it to preserve the sequence correctly.
Solution
You're right that regex is the tool for the job—you just need a pattern that matches both tag segments and plain text segments in the order they appear. Here's a step-by-step fix:
1. Build the Right Regex Pattern
We need to match two distinct types of content in sequence:
- Tags: A tag name (letters only), followed by a colon, followed by a value (no spaces). Pattern:
[a-z]+:[^\s]+ - Plain text: One or more words separated by spaces (anything that isn't a tag). Pattern:
[^\s]+(?:\s+[^\s]+)*
Combine these into a single global, case-insensitive regex that captures both groups:
const queryRegex = /([a-z]+:[^\s]+)|([^\s]+(?:\s+[^\s]+)*)/gi;
2. Parse the Query with the Regex
Use matchAll to iterate through every match, then classify each match as a tag or text and build your array:
function parseSearchQuery(input) { const matches = input.matchAll(queryRegex); const parsedResults = []; for (const [fullMatch, tagMatch, textMatch] of matches) { if (tagMatch) { // Split the tag into type and value const [type, value] = tagMatch.split(':'); parsedResults.push({ type: type.toLowerCase(), value: value.trim() }); } else if (textMatch) { // Clean up whitespace and add as text const cleanedText = textMatch.trim(); if (cleanedText) { parsedResults.push({ type: 'text', value: cleanedText }); } } } return parsedResults; }
3. Test It Out
Let's run your example input through this function:
const testQuery = "author:Stan dogs cats year:1990"; console.log(parseSearchQuery(testQuery)); // Output: // [ // { type: 'author', value: 'Stan' }, // { type: 'text', value: 'dogs cats' }, // { type: 'year', value: '1990' } // ]
Why This Works
- The regex uses the
gflag to find all matches in order, so the sequence from your input is preserved exactly. - The
iflag makes it case-insensitive, soAuthor:StanorAUTHOR:STANwill parse correctly. matchAllgives us access to the capture groups, so we can easily tell if a match is a tag or plain text.- We skip empty text segments (from leading/trailing spaces) to avoid cluttering the array with unnecessary entries.
Once you have this parsed array, your component mapping logic will work perfectly to render the elements in the right order.
内容的提问来源于stack exchange,提问作者dev_n_null

