| // Copyright 2026 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // | |
| #include "formats/bmff/xpath.h" | |
| #include <string> | |
| #include <vector> | |
| #include "absl/strings/match.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_split.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/strings/strip.h" | |
| namespace credentio { | |
| namespace { | |
| // Returns true if the given path matches the pattern. Like, | |
| // "/a[1]" matches "/a[1]"; | |
| // "/a[1]" also matches "/a"; | |
| // but "/a[1]" doesn't match "/a[2]". | |
| // Similarly, "/a[1]/b[2]/c[3]" matches "/a/b/c", "/a[1]/b/c", "/a/b[2]/c", | |
| // "/a/b/c[3]", etc; | |
| // but "/a[2]/b[2]/c[3]" doesn't match "/a[1]/b/c". | |
| // Note that while patterns like "/a/b/c" are expected, we don't expect to see | |
| // paths like "/a/b/c" because positional indexes should always be applied in | |
| // the path. | |
| bool MatchXPath(absl::string_view path, absl::string_view pattern) { | |
| if (path == pattern) { | |
| return true; | |
| } | |
| std::vector<absl::string_view> terms_a = absl::StrSplit(path, '/'); | |
| std::vector<absl::string_view> terms_b = absl::StrSplit(pattern, '/'); | |
| if (terms_a.size() != terms_b.size()) { | |
| return false; | |
| } | |
| for (int i = 0; i < terms_a.size(); ++i) { | |
| if (terms_a[i] == terms_b[i]) { | |
| continue; | |
| } | |
| if (absl::StrContains(terms_b[i], "[")) { | |
| // Term b is a positioned element; requires exact match. | |
| return false; | |
| } | |
| absl::string_view remainder = terms_a[i]; | |
| if (absl::ConsumePrefix(&remainder, terms_b[i]) && | |
| absl::StartsWith(remainder, "[")) { | |
| continue; | |
| } | |
| return false; | |
| } | |
| return true; | |
| }; | |
| } // namespace | |
| bool XPathMatcher::Matches(absl::string_view path) const { | |
| return MatchXPath(path, pattern_); | |
| } | |
| std::string XPath::ToString() const { | |
| std::string result; | |
| for (const XPath* cursor = this; cursor != nullptr; | |
| cursor = cursor->parent_) { | |
| std::string element_name = | |
| absl::StrCat("/", cursor->name_, | |
| cursor->position_.has_value() | |
| ? absl::StrCat("[", *cursor->position_, "]") | |
| : ""); | |
| result = absl::StrCat(element_name, result); | |
| } | |
| return result; | |
| } | |
| } // namespace credentio |