| | 1 | | using System.Collections.Generic; |
| | 2 | | using System.Linq; |
| | 3 | | using System.Text; |
| | 4 | | using Itinero.Instructions.Types; |
| | 5 | | using Itinero.Network.Attributes; |
| | 6 | |
|
| | 7 | | namespace Itinero.Instructions.ToText; |
| | 8 | |
|
| | 9 | | /*** |
| | 10 | | * Instruction to text changes an instruction object into text based on simple substitution. |
| | 11 | | * It uses reflection to create a dictionary of all available fields, which are substituted |
| | 12 | | */ |
| | 13 | | internal class SubstituteText : IInstructionToText |
| | 14 | | { |
| | 15 | | private readonly string _context; |
| | 16 | | private readonly bool _crashOnMissingKey; |
| | 17 | |
|
| | 18 | | private readonly Dictionary<string, IInstructionToText>? |
| | 19 | | _extensions; // extra "fields" to convert this into a string |
| | 20 | |
|
| | 21 | | private readonly Box<IInstructionToText>? _nestedToText; |
| | 22 | | private readonly IEnumerable<(string textOrVarName, bool substitute)> _text; |
| | 23 | |
|
| 175 | 24 | | public SubstituteText( |
| 175 | 25 | | IEnumerable<(string textOrVarName, bool substitute)> text, |
| 175 | 26 | | Box<IInstructionToText>? nestedToText = null, |
| 175 | 27 | | string context = "context not set", |
| 175 | 28 | | Dictionary<string, IInstructionToText>? extensions = null, |
| 175 | 29 | | bool crashOnMissingKey = true |
| 175 | 30 | | ) |
| 175 | 31 | | { |
| 175 | 32 | | var allTexts = new List<(string textOrVarName, bool substitute)>(); |
| 967 | 33 | | foreach (var (txt, subs) in text) |
| 221 | 34 | | { |
| 221 | 35 | | allTexts.Add((subs ? txt.ToLower() : txt, subs)); |
| 221 | 36 | | } |
| | 37 | |
|
| 175 | 38 | | _text = allTexts; |
| 175 | 39 | | _nestedToText = nestedToText; |
| 175 | 40 | | _context = context; |
| 175 | 41 | | _extensions = extensions; |
| 175 | 42 | | _crashOnMissingKey = crashOnMissingKey; |
| 175 | 43 | | } |
| | 44 | |
|
| | 45 | | public string? ToText(BaseInstruction instruction) |
| 191 | 46 | | { |
| 191 | 47 | | var subsValues = new Dictionary<string, object>(); |
| | 48 | |
|
| 3135 | 49 | | foreach (var f in instruction.GetType().GetProperties()) |
| 1281 | 50 | | { |
| 1281 | 51 | | if (!f.CanRead) |
| 0 | 52 | | { |
| 0 | 53 | | continue; |
| | 54 | | } |
| | 55 | |
|
| 1281 | 56 | | subsValues[f.Name.ToLower()] = f.GetValue(instruction); |
| 1281 | 57 | | } |
| | 58 | |
|
| 191 | 59 | | var resultText = new StringBuilder(); |
| 1034 | 60 | | foreach (var (text, substitute) in _text) |
| 232 | 61 | | { |
| 232 | 62 | | if (substitute) |
| 112 | 63 | | { |
| 112 | 64 | | var firstChar = text.ToCharArray()[0]; |
| 112 | 65 | | if (firstChar == '.' || firstChar == '+' || firstChar == '-') |
| 7 | 66 | | { |
| 7 | 67 | | var key = text.Substring(1); |
| 7 | 68 | | var index = 0; |
| 7 | 69 | | switch (firstChar) |
| | 70 | | { |
| | 71 | | case '+': |
| 2 | 72 | | index = instruction.ShapeIndexEnd; |
| 2 | 73 | | break; |
| | 74 | | case '-': |
| 0 | 75 | | index = instruction.ShapeIndex - 1; |
| 0 | 76 | | break; |
| | 77 | | case '.': |
| 5 | 78 | | index = instruction.ShapeIndex; |
| 5 | 79 | | break; |
| | 80 | | } |
| | 81 | |
|
| 7 | 82 | | if (index >= instruction.Route?.Meta?.Count) |
| 0 | 83 | | { |
| 0 | 84 | | return null; |
| | 85 | | } |
| | 86 | |
|
| 7 | 87 | | var segment = instruction.Route?.Meta?[index]?.Attributes; |
| 7 | 88 | | if (segment == null || !segment.TryGetValue(key, out var v)) |
| 3 | 89 | | { |
| 3 | 90 | | if (_crashOnMissingKey) |
| 0 | 91 | | { |
| 0 | 92 | | throw new KeyNotFoundException("The segment does not contain a key " + text + |
| 0 | 93 | | ". The context is " + _context); |
| | 94 | | } |
| | 95 | |
|
| 3 | 96 | | return null; |
| | 97 | | } |
| | 98 | |
|
| 4 | 99 | | resultText.Append(v); |
| 4 | 100 | | } |
| 105 | 101 | | else if (subsValues.TryGetValue(text, out var newValue)) |
| 98 | 102 | | { |
| 98 | 103 | | if (newValue is BaseInstruction instr) |
| 0 | 104 | | { |
| 0 | 105 | | resultText.Append(_nestedToText.Content.ToText(instr)); |
| 0 | 106 | | } |
| | 107 | | else |
| 98 | 108 | | { |
| 98 | 109 | | resultText.Append(newValue); |
| 98 | 110 | | } |
| 98 | 111 | | } |
| 7 | 112 | | else if (_extensions != null && _extensions.TryGetValue(text, out var subs)) |
| 7 | 113 | | { |
| 7 | 114 | | resultText.Append(subs.ToText(instruction)); |
| 7 | 115 | | } |
| 0 | 116 | | else if (_crashOnMissingKey) |
| 0 | 117 | | { |
| 0 | 118 | | throw new KeyNotFoundException( |
| 0 | 119 | | $"The instruction of type {instruction.Type} does not contain a field or extension with name {te |
| | 120 | | } |
| | 121 | | else |
| 0 | 122 | | { |
| 0 | 123 | | return null; |
| | 124 | | } |
| 109 | 125 | | } |
| | 126 | | else |
| 120 | 127 | | { |
| 120 | 128 | | resultText.Append(text); |
| 120 | 129 | | } |
| 229 | 130 | | } |
| | 131 | |
|
| 188 | 132 | | return resultText.ToString(); |
| 191 | 133 | | } |
| | 134 | |
|
| | 135 | | public override string ToString() |
| 0 | 136 | | { |
| 0 | 137 | | return string.Join("", _text.Select(txt => txt.textOrVarName)); |
| 0 | 138 | | } |
| | 139 | |
|
| | 140 | | public int SubstitutedValueCount() |
| 14 | 141 | | { |
| 28 | 142 | | return _text.Count(v => v.substitute); |
| 14 | 143 | | } |
| | 144 | | } |