* Return a path where "/./" or "/../" sequences are removed. * * No filesystem access is done. * * @param svPath Input path * @param sep1 Path separator (typically slash or backslash) * @param sep2 Secondary path separator (typically slash or backslash), or NUL * @return compacted path * * @since GDAL 3.13 */
| 1823 | * @since GDAL 3.13 |
| 1824 | */ |
| 1825 | std::string CPLLexicallyNormalize(std::string_view svPath, char sep1, char sep2) |
| 1826 | { |
| 1827 | struct Token |
| 1828 | { |
| 1829 | size_t iStart = 0; // index of start of token with svPath |
| 1830 | size_t nLen = 0; // length of token (excluding ending separator) |
| 1831 | char chSep = 0; // separator at end of token, or 0 if there is none |
| 1832 | }; |
| 1833 | |
| 1834 | std::vector<Token> tokens; |
| 1835 | |
| 1836 | const auto CompactTokens = [&tokens, &svPath]() |
| 1837 | { |
| 1838 | Token &t = tokens.back(); |
| 1839 | if (t.nLen == 1 && svPath[t.iStart] == '.') |
| 1840 | { |
| 1841 | tokens.pop_back(); |
| 1842 | } |
| 1843 | else if (t.nLen == 2 && svPath[t.iStart] == '.' && |
| 1844 | svPath[t.iStart + 1] == '.') |
| 1845 | { |
| 1846 | if (tokens.size() >= 2) |
| 1847 | tokens.resize(tokens.size() - 2); |
| 1848 | } |
| 1849 | }; |
| 1850 | |
| 1851 | bool lastCharIsSep = false; |
| 1852 | for (size_t i = 0; i < svPath.size(); ++i) |
| 1853 | { |
| 1854 | const char c = svPath[i]; |
| 1855 | if (c == sep1 || c == sep2) |
| 1856 | { |
| 1857 | if (!lastCharIsSep) |
| 1858 | { |
| 1859 | if (tokens.empty()) |
| 1860 | { |
| 1861 | Token t; |
| 1862 | t.chSep = c; |
| 1863 | tokens.push_back(t); |
| 1864 | } |
| 1865 | else |
| 1866 | { |
| 1867 | Token &t = tokens.back(); |
| 1868 | t.chSep = c; |
| 1869 | CompactTokens(); |
| 1870 | } |
| 1871 | lastCharIsSep = true; |
| 1872 | } |
| 1873 | } |
| 1874 | else |
| 1875 | { |
| 1876 | if (tokens.empty() || lastCharIsSep) |
| 1877 | { |
| 1878 | Token t; |
| 1879 | t.iStart = i; |
| 1880 | t.nLen = 1; |
| 1881 | tokens.push_back(t); |
| 1882 | } |