(label)
| 46 | |
| 47 | // Parse one YAML node label like: `button "Save"`, `textbox "Email" [focused]`, `heading "Title" [level=2]` |
| 48 | function parseLabel(label) { |
| 49 | if (!label) return null |
| 50 | const trimmed = label.trim() |
| 51 | const roleMatch = trimmed.match(/^(\w+)/) |
| 52 | if (!roleMatch) return null |
| 53 | const role = roleMatch[1].toLowerCase() |
| 54 | let rest = trimmed.slice(roleMatch[0].length) |
| 55 | |
| 56 | let name |
| 57 | const nameMatch = rest.match(/^\s*"((?:[^"\\]|\\.)*)"/) || rest.match(/^\s*'((?:[^'\\]|\\.)*)'/) |
| 58 | if (nameMatch) { |
| 59 | name = nameMatch[1] |
| 60 | rest = rest.slice(nameMatch[0].length) |
| 61 | } |
| 62 | |
| 63 | const attributes = {} |
| 64 | const attrMatch = rest.match(/\[([^\]]*)\]/) |
| 65 | if (attrMatch) { |
| 66 | for (const tok of attrMatch[1].split(/[\s,]+/).filter(Boolean)) { |
| 67 | const eq = tok.indexOf('=') |
| 68 | if (eq === -1) { |
| 69 | attributes[tok.toLowerCase()] = true |
| 70 | continue |
| 71 | } |
| 72 | attributes[tok.slice(0, eq).trim().toLowerCase()] = unquote(tok.slice(eq + 1)) |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | return { role, name, attributes } |
| 77 | } |
| 78 | |
| 79 | function yamlItemToNode(item) { |
| 80 | if (typeof item === 'string') { |
no test coverage detected