diff --git a/mkdocs/labs.md b/mkdocs/labs.md index 47df420..a3e8c97 100644 --- a/mkdocs/labs.md +++ b/mkdocs/labs.md @@ -58,6 +58,8 @@ At this stage, the query only contains the document itself. - Pandoc(Meta({}), [Para([Str('Hello'), Space(), Str('world!')])]) ``` +### Types + The `find` method allows to select items within the initial collection. To begin with, we can search items by type: @@ -137,6 +139,15 @@ If the list of arguments is empty, there is no match: >>> q.find() ``` +In a boolean context, a query with no results is considered `False` + +```python +>>> bool(q.find()) +False +>>> if not q.find(): +... print("no result") +no result +``` To add match several conditions at once, the `filter` method can be used: @@ -145,18 +156,25 @@ To add match several conditions at once, the `filter` method can be used: - Str('Hello') - Str('world!') ``` +The `filter` method can be used implicitly: a query is callable +```python +>>> q.find(Inline)(Str) +- Str('Hello') +- Str('world!') +``` We can also match the negation of a condition ```python ->>> q.find(Inline).filter(not_(Space)) +>>> q.find(Inline)(not_(Space)) - Str('Hello') - Str('world!') ``` -### Predicates +### Tests -Type are not the only conditions we can check, predicates can be used too: +Types are not the only possible selectors, predicates -- functions that take +a pandoc element and return a boolean value -- can be used too: ```python >>> def startswith_H(elt): @@ -166,14 +184,85 @@ Type are not the only conditions we can check, predicates can be used too: - Str('Hello') ``` +You can use predicate to define and select "virtual types" in a document. +For example, + +```python +def AttrHolder(elt): + return isinstance(elt, (Code, Link, Image, Span, Div, CodeBlock, Header, Table)) +``` + +**TODO:** match by attributes (id, classes, key-values); use keyword arguments +in find with "or" semantics for lists; allow for predicates. For key values +match, match for key existence, key-value pair, predicate as a whole or just +for value. +### Navigation +**TODO.** Parent, children, next, previous, next_sibling, previous_sibling. +```python +>>> q +- Pandoc(Meta({}), [Para([Str('Hello'), Space(), Str('world!')])]) +>>> q.next +- Meta({}) +>>> q.next.next +- {} +>>> q.next.next.next +- [Para([Str('Hello'), Space(), Str('world!')])] +>>> q.next.next.next.next +- Para([Str('Hello'), Space(), Str('world!')]) +>>> q.next.next.next.next.next +- [Str('Hello'), Space(), Str('world!')] +>>> q.next.next.next.next.next.next +- Str('Hello') +>>> q.next.next.next.next.next.next.next +- 'Hello' +>>> q.next.next.next.next.next.next.next.next +- Space() +>>> q.next.next.next.next.next.next.next.next.next +- Str('world!') +>>> q.next.next.next.next.next.next.next.next.next.next +- 'world!' +>>> q.next.next.next.next.next.next.next.next.next.next.next + +``` +```python +>>> q.find(str) +- 'Hello' +- 'world!' +>>> q.find(str)[1] +- 'world!' +>>> w = q.find(str)[1] +>>> w.previous +- Str('world!') +>>> w.previous.previous +- Space() +>>> w.previous.previous.previous +- 'Hello' +>>> w.previous.previous.previous.previous +- Str('Hello') +>>> w.previous.previous.previous.previous.previous +- [Str('Hello'), Space(), Str('world!')] +>>> w.previous.previous.previous.previous.previous.previous +- Para([Str('Hello'), Space(), Str('world!')]) +>>> w.previous.previous.previous.previous.previous.previous.previous +- [Para([Str('Hello'), Space(), Str('world!')])] +>>> w.previous.previous.previous.previous.previous.previous.previous.previous +- {} +>>> w.previous.previous.previous.previous.previous.previous.previous.previous.previous +- Meta({}) +>>> w.previous.previous.previous.previous.previous.previous.previous.previous.previous.previous +- Pandoc(Meta({}), [Para([Str('Hello'), Space(), Str('world!')])]) +>>> w.previous.previous.previous.previous.previous.previous.previous.previous.previous.previous.previous + +``` +-------------------------------------------------------------------------------- Nota: finding lists of inlines is difficult; finding *non-empty* lists of diff --git a/src/pandoc/labs.py b/src/pandoc/labs.py index 4689851..e7335da 100644 --- a/src/pandoc/labs.py +++ b/src/pandoc/labs.py @@ -94,13 +94,14 @@ def _getitem(sequence, indices): return sequence[indices] class Query: - def __init__(self, *results): + def __init__(self, results): self._elts = [] - for result in results: - if isinstance(result, Query): - self._elts.extend(result._elts) # Mmmm ownership issues - else: # "raw results": list of (elt, path) - self._elts.extend(result) + if isinstance(results, tuple): + results = [results] + if isinstance(results, Query): + self._elts.extend(results._elts) # Mmmm ownership issues. Copy? + else: # "raw results": list of (elt, path) + self._elts.extend(results) # ℹī¸ The call `find(object)` is public and equivalent to `_iter()`. def _iter(self): @@ -165,6 +166,8 @@ def get_child(self, i): if isinstance(i, int): try: + if i < 0: + i += len(elt) child = children[i] results.append((child, elt_path.copy() + [(elt, i)])) except IndexError: @@ -185,8 +188,6 @@ def get_parent(self): parent = property(get_parent) - # 🚧 TODO: next and previous - def get_next(self): results = [] for elt, elt_path in self._elts: @@ -215,6 +216,33 @@ def get_next(self): next = property(get_next) + # 🚧 TODO: previous + + def get_previous(self): + results = [] + for elt, elt_path in self._elts: + + # TODO: try deeper in the previous sibling first + # or the previous sibling + # or of there is no previous sibling, the parent + + previous_sibling = Query([(elt, elt_path)]).previous_sibling + if previous_sibling: + last_child = previous_sibling + while child := last_child.get_child(-1): + last_child = child + pass + results.append(last_child._elts[0]) + elif parent := Query([(elt, elt_path)]).parent: + results.append(parent._elts[0]) + else: + pass + + return Query(results) + + previous = property(get_previous) + + def get_next_sibling(self): indices = [path[-1][1] for elt, path in self._elts if path != []] results = [] @@ -240,9 +268,9 @@ def get_previous_sibling(self): pass return Query(results) - prev_sibling = property(get_previous_sibling) + previous_sibling = property(get_previous_sibling) - # Query container (hide path info) + # Query container (hide path info). Or maybe not? Be more explicit? # -------------------------------------------------------------------------- def __len__(self): return len(self._elts) @@ -251,9 +279,9 @@ def __bool__(self): return len(self._elts) != 0 def __getitem__(self, i): - return Query([self._elts[i]]) + return Query(self._elts[i]) - def __iter__(self): + def __iter__(self): # unwrap or not? Mmmm maybe no. return (elt for elt, _ in self._elts) def __repr__(self):