Hello friends!
I’m opening a new topic because I solved the previous formatting issue with my Sanskrit sorting code.
I managed to handle complex formatting using text_portion properties, which seems to work well. I was happy to be reaching my objective, but unfortunately, I lost a crucial functionality in the process, and I’m struggling to re-implement it.
Specifically, I need my functions to sort a selection of text, and if there’s no selection, to sort the entire document. I previously had this functionality working.
Now, I’m sharing two versions of the code. The first one is my latest version, which handles formatting correctly but only sorts everything in the document. The second one is an earlier version that does not handle formatting but allows sorting only the selected text, leaving the rest of the document unchanged.
Both versions also support the Khotanese alphabetical order. The key function to look at is sort_writer_document_xml()
.
First code (shining new formatting) :
import uno
import unohelper
from com.sun.star.task import XJobExecutor
from com.sun.star.beans import PropertyValue
import unicodedata
import re
import xml.etree.ElementTree as ET
from io import StringIO
ID_EXTENSION = 'org.indoiranian.wordlist.sorter'
SERVICE = ('com.sun.star.task.Job',)
SUBSCRIPT_HOOKS = ["'", "’", "ʾ"] # Characters to be treated as subscript_hook
def normalize_khotanese(word):
word = unicodedata.normalize('NFD', word)
char_map = {
'\u0328': '', '\u0105': 'a', '\u012F': 'i', '\u0173': 'u', '\u0119': 'e', '\u01EB': 'o',
'\u0104': 'A', '\u012E': 'I', '\u0172': 'U', '\u0118': 'E', '\u01EA': 'O',
}
normalized = ''.join(char_map.get(c, c) for c in word)
return unicodedata.normalize('NFC', normalized)
def normalize_special_vowels(word):
special_vowels = {
'a\u0304\u0306': 'a_l+b', '\u0101\u0306': 'a_l+b',
'i\u0304\u0306': 'i_l+b', '\u012b\u0306': 'i_l+b',
'u\u0304\u0306': 'u_l+b', '\u016b\u0306': 'u_l+b',
'A\u0304\u0306': 'A_l+b', '\u0100\u0306': 'A_l+b',
'I\u0304\u0306': 'I_l+b', '\u012A\u0306': 'I_l+b',
'U\u0304\u0306': 'U_l+b', '\u016A\u0306': 'U_l+b',
}
for combo, replacement in special_vowels.items():
word = word.replace(combo, replacement)
return word
def sort_words_in_document(language):
try:
localContext = uno.getComponentContext()
smgr = localContext.ServiceManager
desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", localContext)
model = desktop.getCurrentComponent()
if hasattr(model, "Sheets"):
sort_calc_document(model, language)
elif hasattr(model, "Text"):
sort_writer_document_xml(model, language)
else:
raise Exception("This document is neither a Calc spreadsheet nor a Writer document.")
except Exception as e:
print(f"Error during sorting: {e}")
def sort_calc_document(model, language):
try:
sheet = model.CurrentController.ActiveSheet
cursor = sheet.createCursor()
cursor.gotoEndOfUsedArea(True)
data = cursor.getDataArray()
if not data or len(data) == 0:
print("No data found in the sheet.")
return
sorted_data = sorted(data, key=lambda row: custom_sort(str(row[0]), language))
target_range = sheet.getCellRangeByPosition(0, 0, len(data[0])-1, len(data)-1)
target_range.setDataArray(sorted_data)
print("Calc document sorted successfully.")
except Exception as e:
print(f"Error in sorting Calc document: {e}")
def sort_writer_document_xml(model, language):
try:
print("Starting sort_writer_document_xml")
text = model.Text
# Get all paragraphs
paragraphs = []
enum = text.createEnumeration()
while enum.hasMoreElements():
paragraph = enum.nextElement()
if paragraph.supportsService("com.sun.star.text.Paragraph"):
paragraphs.append(paragraph)
# Extract text and formatting from paragraphs
paragraph_data = []
for p in paragraphs:
paragraph_content = []
portion_enum = p.createEnumeration()
while portion_enum.hasMoreElements():
text_portion = portion_enum.nextElement()
if text_portion.supportsService("com.sun.star.text.TextPortion"):
content = text_portion.getString()
if content.strip():
properties = {
'CharPosture': text_portion.CharPosture,
'CharWeight': text_portion.CharWeight,
'CharColor': text_portion.CharColor,
'CharUnderline': text_portion.CharUnderline,
'CharBackColor': text_portion.CharBackColor,
'CharFontName': text_portion.CharFontName, # Aggiungiamo il nome del font
'CharHeight': text_portion.CharHeight, # Aggiungiamo la dimensione del carattere
'CharLocale': text_portion.CharLocale, # Aggiungiamo la lingua del carattere
}
paragraph_content.append((content, properties))
if paragraph_content:
paragraph_data.append(paragraph_content)
print(f"Number of paragraphs: {len(paragraph_data)}")
print("Paragraph content:")
for p in paragraph_data:
print([f"{content} ({props})" for content, props in p])
# Sort paragraphs
sorted_paragraphs = sorted(paragraph_data, key=lambda x: custom_sort(''.join(portion[0] for portion in x), language))
# Clear existing content
cursor = text.createTextCursor()
cursor.gotoStart(False)
cursor.gotoEnd(True)
cursor.setString("")
# Insert sorted paragraphs
for paragraph in sorted_paragraphs:
for content, properties in paragraph:
# Insert the content
text.insertString(cursor, content, False)
# Select the inserted text
cursor.goLeft(len(content), True)
# Apply stored properties
for prop, value in properties.items():
cursor.setPropertyValue(prop, value)
# Move cursor to end of inserted text
cursor.goRight(len(content), False)
# Move to next line
text.insertControlCharacter(cursor, 0, False) # 0 is the constant for newline
print("Writer document sorted successfully.")
except Exception as e:
print(f"Error in sorting Writer document: {e}")
import traceback
traceback.print_exc()
def custom_sort(word, language):
if not word:
return ("", "", "", "")
superscript_match = re.match(r'^([⁰¹²³⁴⁵⁶⁷⁸⁹]+°?)', word)
superscript = superscript_match.group(1) if superscript_match else ''
word_without_superscript = re.sub(r'^[⁰¹²³⁴⁵⁶⁷⁸⁹]+°?', '', word)
number = extract_number(word_without_superscript)
word_without_number = remove_leading_numbers(word_without_superscript)
main_word = word_without_number.rstrip('‑-')
has_hyphen = word_without_number.endswith(('‑', '-'))
lowercase_word = to_lowercase_special(main_word)
preprocessed_word = preprocess_word(lowercase_word, language)
mapping = get_mapping(language)
transformed_word = ""
i = 0
while i < len(preprocessed_word):
if i < len(preprocessed_word) - 4 and preprocessed_word[i:i+5] in mapping:
transformed_word += mapping[preprocessed_word[i:i+5]]
i += 5
elif i < len(preprocessed_word) - 3 and preprocessed_word[i:i+4] in mapping:
transformed_word += mapping[preprocessed_word[i:i+4]]
i += 4
elif i < len(preprocessed_word) - 2 and preprocessed_word[i:i+3] in mapping:
transformed_word += mapping[preprocessed_word[i:i+3]]
i += 3
elif i < len(preprocessed_word) - 1 and preprocessed_word[i:i+2] in mapping:
transformed_word += mapping[preprocessed_word[i:i+2]]
i += 2
elif preprocessed_word[i] in mapping:
transformed_word += mapping[preprocessed_word[i]]
i += 1
else:
transformed_word += preprocessed_word[i]
i += 1
superscript_value = ''.join([str('⁰¹²³⁴⁵⁶⁷⁸⁹'.index(c)) for c in superscript if c in '⁰¹²³⁴⁵⁶⁷⁸⁹'])
superscript_value = superscript_value.zfill(5)
hyphen_value = '1' if has_hyphen else '0'
return (transformed_word, superscript_value, hyphen_value, number)
def extract_number(word):
match = re.match(r'^([0-9¹²³]+)', word)
return match.group(1) if match else ''
def remove_leading_numbers(word):
return re.sub(r'^[0-9¹²³°]+\s*', '', word)
def remove_parentheses(word):
return re.sub(r'\([^)]*\)', '', word)
def to_lowercase_special(word):
lowercase_map = {
'A': 'a', 'Ā': 'ā', 'I': 'i', 'Ī': 'ī', 'U': 'u', 'Ū': 'ū',
'Ṛ': 'ṛ', 'Ṝ': 'ṝ', 'Ḷ': 'ḷ', 'E': 'e', 'O': 'o',
'K': 'k', 'G': 'g', 'Ṅ': 'ṅ', 'C': 'c', 'J': 'j', 'Ñ': 'ñ',
'Ṭ': 'ṭ', 'Ḍ': 'ḍ', 'Ṇ': 'ṇ', 'T': 't', 'D': 'd', 'N': 'n',
'P': 'p', 'B': 'b', 'M': 'm', 'Y': 'y', 'R': 'r', 'L': 'l',
'V': 'v', 'Ś': 'ś', 'Ṣ': 'ṣ', 'S': 's', 'H': 'h',
'Ai': 'ai', 'Au': 'au', 'Ei': 'ei',
'Ph': 'ph', 'Th': 'th', 'Ch': 'ch', 'Kh': 'kh', 'Gh': 'gh',
'Gy': 'gy', 'Ky': 'ky',
'A_l+b': 'a_l+b', 'I_l+b': 'i_l+b', 'U_l+b': 'u_l+b',
'A\u0304\u0306': 'a_l+b', '\u0100\u0306': 'a_l+b',
'I\u0304\u0306': 'i_l+b', '\u012A\u0306': 'i_l+b',
'U\u0304\u0306': 'u_l+b', '\u016A\u0306': 'u_l+b',
}
for multi, lower in lowercase_map.items():
if len(multi) > 1 or '_' in multi or r'\u' in multi:
word = word.replace(multi, lower)
return ''.join(lowercase_map.get(c, c.lower()) for c in word)
def preprocess_word(word, language):
word = re.sub(r"[\(\)\[\]\{\}\*°:‐]", "", word)
word = remove_parentheses(word)
word = normalize_special_vowels(word)
if language == "sanskrit":
return preprocess_sanskrit_word(word)
elif language == "khotanese":
word = normalize_khotanese(word)
word = word.replace('r̥', 'rä')
word = word.replace('ṃ', '')
return word.strip()
def preprocess_sanskrit_word(word):
preprocessed_word = ""
i = 0
while i < len(word):
if word[i] == 'ṃ':
if i < len(word) - 1 and word[i + 1] in 'kgcṭtdpb':
nasal_replacements = {'k': 'ṅ', 'g': 'ṅ', 'c': 'ñ', 'ṭ': 'ṇ', 't': 'n', 'd': 'n', 'p': 'm', 'b': 'm'}
preprocessed_word += nasal_replacements.get(word[i + 1], 'ṃ')
else:
preprocessed_word += 'ṃ'
else:
preprocessed_word += word[i]
i += 1
return preprocessed_word
def get_mapping(language):
if language == "sanskrit":
return {
'ai': '11', 'au': '13', 'kh': '17', 'gh': '19', 'ch': '22', 'jh': '24',
'ṭh': '27', 'ḍh': '29', 'th': '32', 'dh': '34', 'ph': '37', 'bh': '39',
'a': '01', 'ā': '02', 'i': '03', 'ī': '04', 'u': '05', 'ū': '06',
'ṛ': '07', 'ṝ': '08', 'ḷ': '09', 'e': '10', 'o': '12',
'ṃ': '14', 'ḥ': '15', 'k': '16', 'g': '18',
'ṅ': '20', 'c': '21', 'j': '23',
'ñ': '25', 'ṭ': '26', 'ḍ': '28', 'ṇ': '30',
't': '31', 'd': '33', 'n': '35', 'p': '36',
'b': '38', 'm': '40', 'y': '41', 'r': '42',
'l': '43', 'v': '44', 'ś': '45', 'ṣ': '46', 's': '47', 'h': '48',
}
elif language == "khotanese":
return {
'a_l+b': '02', 'i_l+b': '05', 'u_l+b': '07',
'ai': '08', 'au': '09', 'ei': '08', 'kh': '11', 'gh': '12', 'gg': '12',
'ky': '14', 'ch': '15', 'gy': '16', 'ṭh': '19', 'tt': '22', 'th': '23',
'dh': '24', 'ph': '27', 'bh': '28', 'rr': '31', 'śś': '34', 'ṣṣ': '35',
'a': '01', 'ā': '02', 'ä': '03',
'i': '04', 'ī': '05',
'u': '06', 'ū': '07',
'e': '08', 'o': '09',
'k': '10', 'g': '12', 'ṅ': '13', 'c': '14',
'j': '16', 'ñ': '17', 'ṭ': '18', 'ḍ': '20', 'ṇ': '21',
't': '22', 'd': '24', 'n': '25', 'p': '26',
'b': '28', 'm': '29', 'y': '30', 'r': '31',
'l': '32', 'v': '33', 'ś': '34', 'ṣ': '35', 's': '36', 'h': '37',
}
else:
raise ValueError(f"Unsupported language: {language}")
class SortWordsExtension(unohelper.Base, XJobExecutor):
def __init__(self, ctx):
self.ctx = ctx
print("SortWordsExtension initialized")
def trigger(self, event):
print(f"Triggered by event: {event}")
try:
if event == "execute_sanskrit":
sort_words_in_document("sanskrit")
elif event == "execute_khotanese":
sort_words_in_document("khotanese")
else:
print(f"Unknown event: {event}")
except Exception as e:
print(f"Error in trigger: {e}")
# Register the implementation
g_ImplementationHelper = unohelper.ImplementationHelper()
g_ImplementationHelper.addImplementation(SortWordsExtension, ID_EXTENSION, SERVICE)
Second code : Dear old selection-allowing
import uno
import unohelper
from com.sun.star.task import XJobExecutor
import unicodedata
import re
ID_EXTENSION = 'org.indoiranian.wordlist.sorter'
SERVICE = ('com.sun.star.task.Job',)
SUBSCRIPT_HOOKS = ["'", "’", "ʾ"] # Characters to be treated as subscript_hook
def normalize_khotanese(word):
word = unicodedata.normalize('NFD', word)
char_map = {
'\u0328': '', '\u0105': 'a', '\u012F': 'i', '\u0173': 'u', '\u0119': 'e', '\u01EB': 'o',
'\u0104': 'A', '\u012E': 'I', '\u0172': 'U', '\u0118': 'E', '\u01EA': 'O',
}
normalized = ''.join(char_map.get(c, c) for c in word)
return unicodedata.normalize('NFC', normalized)
def normalize_special_vowels(word):
special_vowels = {
'a\u0304\u0306': 'a_l+b', '\u0101\u0306': 'a_l+b',
'i\u0304\u0306': 'i_l+b', '\u012b\u0306': 'i_l+b',
'u\u0304\u0306': 'u_l+b', '\u016b\u0306': 'u_l+b',
'A\u0304\u0306': 'A_l+b', '\u0100\u0306': 'A_l+b',
'I\u0304\u0306': 'I_l+b', '\u012A\u0306': 'I_l+b',
'U\u0304\u0306': 'U_l+b', '\u016A\u0306': 'U_l+b',
}
for combo, replacement in special_vowels.items():
word = word.replace(combo, replacement)
return word
def sort_words_in_document(language):
try:
localContext = uno.getComponentContext()
smgr = localContext.ServiceManager
desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", localContext)
model = desktop.getCurrentComponent()
if hasattr(model, "Sheets"):
sort_calc_document(model, language)
elif hasattr(model, "Text"):
sort_writer_document(model, language)
else:
raise Exception("This document is neither a Calc spreadsheet nor a Writer document.")
except Exception as e:
print(f"Error during sorting: {e}")
def sort_calc_document(model, language):
try:
sheet = model.CurrentController.ActiveSheet
cursor = sheet.createCursor()
cursor.gotoEndOfUsedArea(True)
data = cursor.getDataArray()
if not data or len(data) == 0:
print("No data found in the sheet.")
return
sorted_data = sorted(data, key=lambda row: custom_sort(str(row[0]), language))
target_range = sheet.getCellRangeByPosition(0, 0, len(data[0])-1, len(data)-1)
target_range.setDataArray(sorted_data)
print("Calc document sorted successfully.")
except Exception as e:
print(f"Error in sorting Calc document: {e}")
def sort_writer_document(model, language):
try:
text = model.Text
cursor = model.CurrentController.getViewCursor()
if cursor.isCollapsed():
cursor.gotoStart(False)
cursor.gotoEnd(True)
selected_text = cursor.getString().strip().splitlines()
sorted_lines = sorted(selected_text, key=lambda line: custom_sort(line.split()[0], language) if line.strip() else ("", "", "", ""))
cursor.setString("")
text.insertString(cursor, "\n".join(sorted_lines), False)
print("Writer document sorted successfully.")
except Exception as e:
print(f"Error in sorting Writer document: {e}")
def custom_sort(word, language):
if not word:
return ("", "", "", "")
superscript_match = re.match(r'^([⁰¹²³⁴⁵⁶⁷⁸⁹]+°?)', word)
superscript = superscript_match.group(1) if superscript_match else ''
word_without_superscript = re.sub(r'^[⁰¹²³⁴⁵⁶⁷⁸⁹]+°?', '', word)
number = extract_number(word_without_superscript)
word_without_number = remove_leading_numbers(word_without_superscript)
main_word = word_without_number.rstrip('‑-')
has_hyphen = word_without_number.endswith(('‑', '-'))
lowercase_word = to_lowercase_special(main_word)
preprocessed_word = preprocess_word(lowercase_word, language)
mapping = get_mapping(language)
transformed_word = ""
i = 0
while i < len(preprocessed_word):
if i < len(preprocessed_word) - 4 and preprocessed_word[i:i+5] in mapping:
transformed_word += mapping[preprocessed_word[i:i+5]]
i += 5
elif i < len(preprocessed_word) - 3 and preprocessed_word[i:i+4] in mapping:
transformed_word += mapping[preprocessed_word[i:i+4]]
i += 4
elif i < len(preprocessed_word) - 2 and preprocessed_word[i:i+3] in mapping:
transformed_word += mapping[preprocessed_word[i:i+3]]
i += 3
elif i < len(preprocessed_word) - 1 and preprocessed_word[i:i+2] in mapping:
transformed_word += mapping[preprocessed_word[i:i+2]]
i += 2
elif preprocessed_word[i] in mapping:
transformed_word += mapping[preprocessed_word[i]]
i += 1
else:
transformed_word += preprocessed_word[i]
i += 1
superscript_value = ''.join([str('⁰¹²³⁴⁵⁶⁷⁸⁹'.index(c)) for c in superscript if c in '⁰¹²³⁴⁵⁶⁷⁸⁹'])
superscript_value = superscript_value.zfill(5)
hyphen_value = '1' if has_hyphen else '0'
return (transformed_word, superscript_value, hyphen_value, number)
def extract_number(word):
match = re.match(r'^([0-9¹²³]+)', word)
return match.group(1) if match else ''
def remove_leading_numbers(word):
return re.sub(r'^[0-9¹²³°]+\s*', '', word)
def remove_parentheses(word):
return re.sub(r'\([^)]*\)', '', word)
def to_lowercase_special(word):
lowercase_map = {
'A': 'a', 'Ā': 'ā', 'I': 'i', 'Ī': 'ī', 'U': 'u', 'Ū': 'ū',
'R̥': 'r̥', 'R̥̄': 'r̥̄', 'L̥': 'l̥', 'E': 'e', 'O': 'o',
'K': 'k', 'G': 'g', 'Ṅ': 'ṅ', 'C': 'c', 'J': 'j', 'Ñ': 'ñ',
'Ṭ': 'ṭ', 'Ḍ': 'ḍ', 'Ṇ': 'ṇ', 'T': 't', 'D': 'd', 'N': 'n',
'P': 'p', 'B': 'b', 'M': 'm', 'Y': 'y', 'R': 'r', 'L': 'l',
'V': 'v', 'Ś': 'ś', 'Ṣ': 'ṣ', 'S': 's', 'H': 'h',
'Ai': 'ai', 'Au': 'au', 'Ei': 'ei',
'Ph': 'ph', 'Th': 'th', 'Ch': 'ch', 'Kh': 'kh', 'Gh': 'gh',
'Gy': 'gy', 'Ky': 'ky',
'A_l+b': 'a_l+b', 'I_l+b': 'i_l+b', 'U_l+b': 'u_l+b',
'A\u0304\u0306': 'a_l+b', '\u0100\u0306': 'a_l+b',
'I\u0304\u0306': 'i_l+b', '\u012A\u0306': 'i_l+b',
'U\u0304\u0306': 'u_l+b', '\u016A\u0306': 'u_l+b',
}
for multi, lower in lowercase_map.items():
if len(multi) > 1 or '_' in multi or r'\u' in multi:
word = word.replace(multi, lower)
return ''.join(lowercase_map.get(c, c.lower()) for c in word)
def preprocess_word(word, language):
word = re.sub(r"[\(\)\[\]\{\}\*°:‐]", "", word)
word = remove_parentheses(word)
word = normalize_special_vowels(word)
if language == "sanskrit":
return preprocess_sanskrit_word(word)
elif language == "khotanese":
word = normalize_khotanese(word)
word = word.replace('r̥', 'rä')
word = word.replace('ṃ', '')
return word.strip()
def preprocess_sanskrit_word(word):
preprocessed_word = ""
i = 0
while i < len(word):
if word[i] == 'ṃ':
if i < len(word) - 1 and word[i + 1] in 'kgcṭtdpb':
nasal_replacements = {'k': 'ṅ', 'g': 'ṅ', 'c': 'ñ', 'ṭ': 'ṇ', 't': 'n', 'd': 'n', 'p': 'm', 'b': 'm'}
preprocessed_word += nasal_replacements.get(word[i + 1], 'ṃ')
else:
preprocessed_word += 'ṃ'
else:
preprocessed_word += word[i]
i += 1
return preprocessed_word
def get_mapping(language):
if language == "sanskrit":
return {
'ai': '11', 'au': '13', 'kh': '17', 'gh': '19', 'ch': '22', 'jh': '24',
'ṭh': '27', 'ḍh': '29', 'th': '32', 'dh': '34', 'ph': '37', 'bh': '39',
'a': '01', 'ā': '02', 'i': '03', 'ī': '04', 'u': '05', 'ū': '06',
'r̥': '07', 'r̥̄': '08', 'l̥': '09', 'e': '10', 'o': '12',
'ṃ': '14', 'ḥ': '15', 'k': '16', 'g': '18',
'ṅ': '20', 'c': '21', 'j': '23',
'ñ': '25', 'ṭ': '26', 'ḍ': '28', 'ṇ': '30',
't': '31', 'd': '33', 'n': '35', 'p': '36',
'b': '38', 'm': '40', 'y': '41', 'r': '42',
'l': '43', 'v': '44', 'ś': '45', 'ṣ': '46', 's': '47', 'h': '48',
}
elif language == "khotanese":
return {
'a_l+b': '02', 'i_l+b': '05', 'u_l+b': '07',
'ai': '08', 'au': '09', 'ei': '08', 'kh': '11', 'gh': '12', 'gg': '12',
'ky': '14', 'ch': '15', 'gy': '16', 'ṭh': '19', 'tt': '22', 'th': '23',
'dh': '24', 'ph': '27', 'bh': '28', 'rr': '31', 'śś': '34', 'ṣṣ': '35',
'a': '01', 'ā': '02', 'ä': '03',
'i': '04', 'ī': '05',
'u': '06', 'ū': '07',
'e': '08', 'o': '09',
'k': '10', 'g': '12', 'ṅ': '13', 'c': '14',
'j': '16', 'ñ': '17', 'ṭ': '18', 'ḍ': '20', 'ṇ': '21',
't': '22', 'd': '24', 'n': '25', 'p': '26',
'b': '28', 'm': '29', 'y': '30', 'r': '31',
'l': '32', 'v': '33', 'ś': '34', 'ṣ': '35', 's': '36', 'h': '37',
}
else:
raise ValueError(f"Unsupported language: {language}")
class SortWordsExtension(unohelper.Base, XJobExecutor):
def __init__(self, ctx):
self.ctx = ctx
print("SortWordsExtension initialized")
def trigger(self, event):
print(f"Triggered by event: {event}")
try:
if event == "execute_sanskrit":
sort_words_in_document("sanskrit")
elif event == "execute_khotanese":
sort_words_in_document("khotanese")
else:
print(f"Unknown event: {event}")
except Exception as e:
print(f"Error in trigger: {e}")
# Register the implementation
g_ImplementationHelper = unohelper.ImplementationHelper()
g_ImplementationHelper.addImplementation(SortWordsExtension, ID_EXTENSION, SERVICE)
I’m really stuck!! I could really use some help
Thanks