Skip to content

Commit

Permalink
counter management (#28)
Browse files Browse the repository at this point in the history
* counter management

* better scan for if statements

* html statement velocity

* doc and scan speedup

* speedup for and better scanning

* disclamer for heading automatic numbering
  • Loading branch information
philippe-levan authored Nov 20, 2023
1 parent b38c07f commit 3522a9d
Show file tree
Hide file tree
Showing 13 changed files with 472 additions and 70 deletions.
112 changes: 112 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,111 @@ and the odt template :
[endhtml]
```

### counter statement

You can use counter statement in order to display values that will be incremented step by step in your document. It
can be used for example to have a heading automatic numbering.

**WARNING** : generally you don't have to use this counter feature. You can use the automatic numbering of Word or Libre Office

In your odt, you can use :

```
chapter [counter chapter] : introduction
chapter [counter chapter] : context
[counter paragraph] : geopolitical context
[counter paragraph] : geographical context
[counter paragraph] : economical context
chapter [counter chapter] : analysis
[counter.reset paragraph]
[counter paragraph] : geopolitical analysis
[counter paragraph] : geographical analysis
[counter paragraph] : economical analysis
```

It will be transformed in :

```
chapter 1 : introduction
chapter 2 : context
1 : geopolitical context
2 : geographical context
3 : economical context
chapter 3 : analysis
1 : geopolitical analysis
2 : geographical analysis
3 : economical analysis
```

The possible syntaxe are :

* `[counter counter_name]` : increment the counter "counter_name" and display it
* `[counter.reset counter_name]` : reset the counter "counter_name" to 0
* `[counter.last counter_name]` : display the last value of the counter "counter_name" without incrementing it
* `[counter.format counter_name format_name]` : change the format of the counter
* `[counter.format counter_name number]` : the counter is displayed as a number (default)
* `[counter.format counter_name letter_lowercase]` : the counter is displayed as a letter (a, b, c, ...)
* `[counter.format counter_name letter_uppercase]` : the counter is displayed as a letter (A, B, C, ...)

You can display hierarchical counters by just using `counter.last`:


```
chapter [counter chapter] : introduction
chapter [counter chapter] : context
[counter.last chapter].[counter paragraph] : geopolitical context
[counter.last chapter].[counter paragraph] : geographical context
[counter.last chapter].[counter paragraph] : economical context
chapter [counter chapter] : analysis
[counter.reset paragraph]
[counter.last chapter].[counter paragraph] : geopolitical analysis
[counter.last chapter].[counter paragraph] : geographical analysis
[counter.last chapter].[counter paragraph] : economical analysis
```

The result will be

```
chapter 1 : introduction
chapter 2 : context
2.1 : geopolitical context
2.2 : geographical context
2.3 : economical context
chapter 3 : analysis
3.1 : geopolitical analysis
3.2 : geographical analysis
3.3 : economical analysis
```

## Supported formats

Expand Down Expand Up @@ -611,6 +716,13 @@ For trying to fix these problems, you can try:

## Versions :

- v1.4.0, 2023-11-17 : counters
- add a counter system inside templates
- add better scan for if statement. Raises an error if there is too many endif in the template.
- speedup html statement replacement and scanning
- speedup for statement replacement and scanning
- tests of for scanning
- internal : add scan testing inside content unit tests
- v1.3.0, 2023-11-16 :
- major refactoring. No evolution for the user.
- new unit tests on tables and images
Expand Down
162 changes: 162 additions & 0 deletions lotemplate/Statement/CounterStatement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import re
from com.sun.star.lang import XComponent

class CounterStatement:
counter_regex = r"""
\[\s*
(?:
(?:
(counter)
(?:\s+(\w+))
)
|
(?:
(counter\.reset|counter\.last)
(?:\s+(\w+))
)
|
(?:
(counter\.format)
(?:\s+(\w+))
(?:\s+(number|letter_uppercase|letter_lowercase))
)
)
\s*\]
"""
counter_regex = re.sub(r'#.*', '', counter_regex).replace("\n", "").replace("\t", "").replace(" ", "")

def __init__(self, counter_string: str):
self.counter_string = counter_string
match = re.search(self.counter_regex, counter_string, re.IGNORECASE)

if match.group(1) is not None:
self.command_name = match.group(1)
self.counter_name = match.group(2)
elif match.group(3) is not None:
self.command_name = match.group(3)
self.counter_name = match.group(4)
elif match.group(5) is not None:
self.command_name = match.group(5)
self.counter_name = match.group(6)
self.counter_format = match.group(7)

class CounterManager:
"""
Class representing an html statement in a template libreoffice
"""

def __init__(self, html: str, component: XComponent):
self.counter_list = {}

def scan_counter(doc: XComponent) -> None:
"""
scan for counter statement. No return. We just verify that there is
and endif for each if statement
"""
def compute_counter(x_found):
"""
Compute the counter statement.
"""
counter_text = x_found.getText()
counter_cursor = counter_text.createTextCursorByRange(x_found)
cursor_statement = CounterStatement(counter_cursor.String)

def find_counter_to_compute(doc, search, x_found):
"""
Find the if statement to compute.
"""
if x_found is None:
return None

compute_counter(x_found)

# searching for the next counter statement.
x_found_after = doc.findNext(x_found.End, search)
if x_found_after is not None:
find_counter_to_compute(doc, search, x_found_after)

# main of if_replace
search = doc.createSearchDescriptor()
search.SearchString = CounterStatement.counter_regex
search.SearchRegularExpression = True
search.SearchCaseSensitive = False
x_found = doc.findFirst(search)
find_counter_to_compute(doc, search, x_found)

def counter_replace(doc: XComponent) -> None:
"""
scan for counter statement. No return. We just verify that there is
and endif for each if statement
"""
def compute_counter(x_found):
"""
Compute the counter statement.
"""
def number_formated(format: str, value: int) -> str:
if format=='number':
return str(value)
elif format=='letter_uppercase':
# after Z we go to A
value -= 1
value = value % 26
return chr(value + 65)
elif format=='letter_lowercase':
# after z we go to a
value -= 1
value = value % 26
return chr(value + 97)
return str(value)

counter_text = x_found.getText()
counter_cursor = counter_text.createTextCursorByRange(x_found)
cursor_statement = CounterStatement(counter_cursor.String)
if cursor_statement.command_name == 'counter':
if cursor_statement.counter_name not in counter_list:
counter_list[cursor_statement.counter_name] = {
"value": 0,
"format": "number"
}
counter_list[cursor_statement.counter_name]["value"] = counter_list[cursor_statement.counter_name]["value"] + 1
counter_cursor.String = number_formated(counter_list[cursor_statement.counter_name]["format"], counter_list[cursor_statement.counter_name]["value"])
elif cursor_statement.command_name == 'counter.format':
if cursor_statement.counter_name not in counter_list:
counter_list[cursor_statement.counter_name] = {
"value": 0,
"format": cursor_statement.counter_format
}
else:
counter_list[cursor_statement.counter_name]["format"] = cursor_statement.counter_format
counter_cursor.String = ""
elif cursor_statement.command_name == 'counter.reset':
counter_list[cursor_statement.counter_name]["value"] = 0
counter_cursor.String = ""
elif cursor_statement.command_name == 'counter.last':
counter_cursor.String = number_formated(counter_list[cursor_statement.counter_name]["format"], counter_list[cursor_statement.counter_name]["value"])

def find_counter_to_compute(doc, search, x_found):
"""
Find the if statement to compute.
"""
if x_found is None:
return None

text = x_found.getText()
cursor = text.createTextCursorByRange(x_found)
str = cursor.String


compute_counter(x_found)

# searching for the next counter statement.
x_found_after = doc.findNext(x_found.End, search)
if x_found_after is not None:
find_counter_to_compute(doc, search, x_found_after)

# main of counter_replace
counter_list = {}
search = doc.createSearchDescriptor()
search.SearchString = CounterStatement.counter_regex
search.SearchRegularExpression = True
search.SearchCaseSensitive = False
x_found = doc.findFirst(search)
find_counter_to_compute(doc, search, x_found)
Loading

0 comments on commit 3522a9d

Please sign in to comment.