use of com.ichi2.libanki.Models in project Anki-Android by ankidroid.
the class Collection method _renderQA.
@RustCleanup("#8951 - Remove FrontSide added to the front")
@NonNull
public HashMap<String, String> _renderQA(long cid, Model model, long did, int ord, String tags, String[] flist, int flags, boolean browser, String qfmt, String afmt) {
// data is [cid, nid, mid, did, ord, tags, flds, cardFlags]
// unpack fields and create dict
Map<String, Pair<Integer, JSONObject>> fmap = Models.fieldMap(model);
Set<Map.Entry<String, Pair<Integer, JSONObject>>> maps = fmap.entrySet();
Map<String, String> fields = HashUtil.HashMapInit(maps.size() + 8);
for (Map.Entry<String, Pair<Integer, JSONObject>> entry : maps) {
fields.put(entry.getKey(), flist[entry.getValue().first]);
}
int cardNum = ord + 1;
fields.put("Tags", tags.trim());
fields.put("Type", model.getString("name"));
fields.put("Deck", mDecks.name(did));
String baseName = Decks.basename(fields.get("Deck"));
fields.put("Subdeck", baseName);
fields.put("CardFlag", _flagNameFromCardFlags(flags));
JSONObject template;
if (model.isStd()) {
template = model.getJSONArray("tmpls").getJSONObject(ord);
} else {
template = model.getJSONArray("tmpls").getJSONObject(0);
}
fields.put("Card", template.getString("name"));
fields.put(String.format(Locale.US, "c%d", cardNum), "1");
// render q & a
HashMap<String, String> d = HashUtil.HashMapInit(2);
d.put("id", Long.toString(cid));
qfmt = TextUtils.isEmpty(qfmt) ? template.getString("qfmt") : qfmt;
afmt = TextUtils.isEmpty(afmt) ? template.getString("afmt") : afmt;
for (Pair<String, String> p : new Pair[] { new Pair<>("q", qfmt), new Pair<>("a", afmt) }) {
String type = p.first;
String format = p.second;
if ("q".equals(type)) {
format = fClozePatternQ.matcher(format).replaceAll(String.format(Locale.US, "{{$1cq-%d:", cardNum));
format = fClozeTagStart.matcher(format).replaceAll(String.format(Locale.US, "<%%cq:%d:", cardNum));
fields.put("FrontSide", "");
} else {
format = fClozePatternA.matcher(format).replaceAll(String.format(Locale.US, "{{$1ca-%d:", cardNum));
format = fClozeTagStart.matcher(format).replaceAll(String.format(Locale.US, "<%%ca:%d:", cardNum));
// the following line differs from libanki // TODO: why?
// fields.put("FrontSide", mMedia.stripAudio(d.get("q")));
fields.put("FrontSide", d.get("q"));
}
String html;
try {
html = ParsedNode.parse_inner(format).render(fields, "q".equals(type), getContext());
} catch (TemplateError er) {
Timber.w(er);
html = er.message(getContext());
}
html = ChessFilter.fenToChessboard(html, getContext());
if (!browser) {
// browser don't show image. So compiling LaTeX actually remove information.
html = LaTeX.mungeQA(html, this, model);
}
d.put(type, html);
// empty cloze?
if ("q".equals(type) && model.isCloze()) {
if (Models._availClozeOrds(model, flist, false).isEmpty()) {
String link = String.format("<a href=\"%s\">%s</a>", mContext.getResources().getString(R.string.link_ankiweb_docs_cloze_deletion), "help");
System.out.println(link);
d.put("q", mContext.getString(R.string.empty_cloze_warning, link));
}
}
}
return d;
}
use of com.ichi2.libanki.Models in project Anki-Android by ankidroid.
the class CardContentProvider method update.
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
if (!hasReadWritePermission() && shouldEnforceUpdateSecurity(uri)) {
throwSecurityException("update", uri);
}
Collection col = CollectionHelper.getInstance().getCol(mContext);
if (col == null) {
throw new IllegalStateException(COL_NULL_ERROR_MSG);
}
col.log(getLogMessage("update", uri));
// Find out what data the user is requesting
int match = sUriMatcher.match(uri);
// Number of updated entries (return value)
int updated = 0;
switch(match) {
case NOTES_V2:
case NOTES:
throw new IllegalArgumentException("Not possible to update notes directly (only through data URI)");
case NOTES_ID:
{
/* Direct access note details
*/
Note currentNote = getNoteFromUri(uri, col);
// the key of the ContentValues contains the column name
// the value of the ContentValues contains the row value.
Set<Map.Entry<String, Object>> valueSet = values.valueSet();
for (Map.Entry<String, Object> entry : valueSet) {
String key = entry.getKey();
// when the client does not specify FLDS, then don't update the FLDS
if (key.equals(FlashCardsContract.Note.FLDS)) {
// Update FLDS
Timber.d("CardContentProvider: flds update...");
String newFldsEncoded = (String) entry.getValue();
String[] flds = Utils.splitFields(newFldsEncoded);
// Check that correct number of flds specified
if (flds.length != currentNote.getFields().length) {
throw new IllegalArgumentException("Incorrect flds argument : " + newFldsEncoded);
}
// Update the note
for (int idx = 0; idx < flds.length; idx++) {
currentNote.setField(idx, flds[idx]);
}
updated++;
} else if (key.equals(FlashCardsContract.Note.TAGS)) {
// Update tags
Timber.d("CardContentProvider: tags update...");
Object tags = entry.getValue();
if (tags != null) {
currentNote.setTagsFromStr(String.valueOf(tags));
}
updated++;
} else {
// Unsupported column
throw new IllegalArgumentException("Unsupported column: " + key);
}
}
Timber.d("CardContentProvider: Saving note...");
currentNote.flush();
break;
}
case NOTES_ID_CARDS:
// TODO: To be implemented
throw new UnsupportedOperationException("Not yet implemented");
// break;
case NOTES_ID_CARDS_ORD:
{
Card currentCard = getCardFromUri(uri, col);
boolean isDeckUpdate = false;
long did = Decks.NOT_FOUND_DECK_ID;
// the key of the ContentValues contains the column name
// the value of the ContentValues contains the row value.
Set<Map.Entry<String, Object>> valueSet = values.valueSet();
for (Map.Entry<String, Object> entry : valueSet) {
// Only updates on deck id is supported
String key = entry.getKey();
isDeckUpdate = key.equals(FlashCardsContract.Card.DECK_ID);
did = values.getAsLong(key);
}
if (col.getDecks().isDyn(did)) {
throw new IllegalArgumentException("Cards cannot be moved to a filtered deck");
}
/* now update the card
*/
if ((isDeckUpdate) && (did >= 0)) {
Timber.d("CardContentProvider: Moving card to other deck...");
col.getDecks().flush();
currentCard.setDid(did);
currentCard.flush();
col.save();
updated++;
} else {
// User tries an operation that is not (yet?) supported.
throw new IllegalArgumentException("Currently only updates of decks are supported");
}
break;
}
case MODELS:
throw new IllegalArgumentException("Cannot update models in bulk");
case MODELS_ID:
// Get the input parameters
String newModelName = values.getAsString(FlashCardsContract.Model.NAME);
String newCss = values.getAsString(FlashCardsContract.Model.CSS);
String newDid = values.getAsString(FlashCardsContract.Model.DECK_ID);
String newFieldList = values.getAsString(FlashCardsContract.Model.FIELD_NAMES);
if (newFieldList != null) {
// Changing the field names would require a full-sync
throw new IllegalArgumentException("Field names cannot be changed via provider");
}
Integer newSortf = values.getAsInteger(FlashCardsContract.Model.SORT_FIELD_INDEX);
Integer newType = values.getAsInteger(FlashCardsContract.Model.TYPE);
String newLatexPost = values.getAsString(FlashCardsContract.Model.LATEX_POST);
String newLatexPre = values.getAsString(FlashCardsContract.Model.LATEX_PRE);
// Get the original note JSON
Model model = col.getModels().get(getModelIdFromUri(uri, col));
try {
// Update model name and/or css
if (newModelName != null) {
model.put("name", newModelName);
updated++;
}
if (newCss != null) {
model.put("css", newCss);
updated++;
}
if (newDid != null) {
if (col.getDecks().isDyn(Long.parseLong(newDid))) {
throw new IllegalArgumentException("Cannot set a filtered deck as default deck for a model");
}
model.put("did", newDid);
updated++;
}
if (newSortf != null) {
model.put("sortf", newSortf);
updated++;
}
if (newType != null) {
model.put("type", newType);
updated++;
}
if (newLatexPost != null) {
model.put("latexPost", newLatexPost);
updated++;
}
if (newLatexPre != null) {
model.put("latexPre", newLatexPre);
updated++;
}
col.getModels().save(model);
col.save();
} catch (JSONException e) {
Timber.e(e, "JSONException updating model");
}
break;
case MODELS_ID_TEMPLATES:
throw new IllegalArgumentException("Cannot update templates in bulk");
case MODELS_ID_TEMPLATES_ID:
Long mid = values.getAsLong(CardTemplate.MODEL_ID);
Integer ord = values.getAsInteger(CardTemplate.ORD);
String name = values.getAsString(CardTemplate.NAME);
String qfmt = values.getAsString(CardTemplate.QUESTION_FORMAT);
String afmt = values.getAsString(CardTemplate.ANSWER_FORMAT);
String bqfmt = values.getAsString(CardTemplate.BROWSER_QUESTION_FORMAT);
String bafmt = values.getAsString(CardTemplate.BROWSER_ANSWER_FORMAT);
// Throw exception if read-only fields are included
if (mid != null || ord != null) {
throw new IllegalArgumentException("Updates to mid or ord are not allowed");
}
// Update the model
try {
int templateOrd = Integer.parseInt(uri.getLastPathSegment());
Model existingModel = col.getModels().get(getModelIdFromUri(uri, col));
JSONArray templates = existingModel.getJSONArray("tmpls");
JSONObject template = templates.getJSONObject(templateOrd);
if (name != null) {
template.put("name", name);
updated++;
}
if (qfmt != null) {
template.put("qfmt", qfmt);
updated++;
}
if (afmt != null) {
template.put("afmt", afmt);
updated++;
}
if (bqfmt != null) {
template.put("bqfmt", bqfmt);
updated++;
}
if (bafmt != null) {
template.put("bafmt", bafmt);
updated++;
}
// Save the model
templates.put(templateOrd, template);
existingModel.put("tmpls", templates);
col.getModels().save(existingModel, true);
col.save();
} catch (JSONException e) {
throw new IllegalArgumentException("Model is malformed", e);
}
break;
case SCHEDULE:
{
Set<Map.Entry<String, Object>> valueSet = values.valueSet();
int cardOrd = -1;
long noteID = -1;
int ease = -1;
long timeTaken = -1;
int bury = -1;
int suspend = -1;
for (Map.Entry<String, Object> entry : valueSet) {
String key = entry.getKey();
switch(key) {
case FlashCardsContract.ReviewInfo.NOTE_ID:
noteID = values.getAsLong(key);
break;
case FlashCardsContract.ReviewInfo.CARD_ORD:
cardOrd = values.getAsInteger(key);
break;
case FlashCardsContract.ReviewInfo.EASE:
ease = values.getAsInteger(key);
break;
case FlashCardsContract.ReviewInfo.TIME_TAKEN:
timeTaken = values.getAsLong(key);
break;
case FlashCardsContract.ReviewInfo.BURY:
bury = values.getAsInteger(key);
break;
case FlashCardsContract.ReviewInfo.SUSPEND:
suspend = values.getAsInteger(key);
break;
}
}
if (cardOrd != -1 && noteID != -1) {
Card cardToAnswer = getCard(noteID, cardOrd, col);
if (cardToAnswer != null) {
if (bury == 1) {
// bury card
buryOrSuspendCard(col, col.getSched(), cardToAnswer, true);
} else if (suspend == 1) {
// suspend card
buryOrSuspendCard(col, col.getSched(), cardToAnswer, false);
} else {
answerCard(col, col.getSched(), cardToAnswer, ease, timeTaken);
}
updated++;
} else {
Timber.e("Requested card with noteId %d and cardOrd %d was not found. Either the provided " + "noteId/cardOrd were wrong or the card has been deleted in the meantime.", noteID, cardOrd);
}
}
break;
}
case DECKS:
throw new IllegalArgumentException("Can't update decks in bulk");
case DECKS_ID:
// TODO: be sure to throw exception if change to the dyn value of a deck is requested
throw new UnsupportedOperationException("Not yet implemented");
case DECK_SELECTED:
{
Set<Map.Entry<String, Object>> valueSet = values.valueSet();
for (Map.Entry<String, Object> entry : valueSet) {
String key = entry.getKey();
if (key.equals(FlashCardsContract.Deck.DECK_ID)) {
long deckId = values.getAsLong(key);
if (selectDeckWithCheck(col, deckId)) {
updated++;
}
}
}
col.save();
break;
}
default:
// Unknown URI type
throw new IllegalArgumentException("uri " + uri + " is not supported");
}
return updated;
}
use of com.ichi2.libanki.Models in project Anki-Android by ankidroid.
the class CardContentProvider method insert.
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
if (!hasReadWritePermission() && shouldEnforceQueryOrInsertSecurity()) {
throwSecurityException("insert", uri);
}
Collection col = CollectionHelper.getInstance().getCol(mContext);
if (col == null) {
throw new IllegalStateException(COL_NULL_ERROR_MSG);
}
col.log(getLogMessage("insert", uri));
// Find out what data the user is requesting
int match = sUriMatcher.match(uri);
switch(match) {
case NOTES:
{
/* Insert new note with specified fields and tags
*/
Long modelId = values.getAsLong(FlashCardsContract.Note.MID);
String flds = values.getAsString(FlashCardsContract.Note.FLDS);
String tags = values.getAsString(FlashCardsContract.Note.TAGS);
Models.AllowEmpty allowEmpty = Models.AllowEmpty.fromBoolean(values.getAsBoolean(FlashCardsContract.Note.ALLOW_EMPTY));
// Create empty note
com.ichi2.libanki.Note newNote = new com.ichi2.libanki.Note(col, col.getModels().get(modelId));
// Set fields
String[] fldsArray = Utils.splitFields(flds);
// Check that correct number of flds specified
if (fldsArray.length != newNote.getFields().length) {
throw new IllegalArgumentException("Incorrect flds argument : " + flds);
}
for (int idx = 0; idx < fldsArray.length; idx++) {
newNote.setField(idx, fldsArray[idx]);
}
// Set tags
if (tags != null) {
newNote.setTagsFromStr(tags);
}
// Add to collection
col.addNote(newNote, allowEmpty);
col.save();
return Uri.withAppendedPath(FlashCardsContract.Note.CONTENT_URI, Long.toString(newNote.getId()));
}
case NOTES_ID:
// Note ID is generated automatically by libanki
throw new IllegalArgumentException("Not possible to insert note with specific ID");
case NOTES_ID_CARDS:
case NOTES_ID_CARDS_ORD:
// Cards are generated automatically by libanki
throw new IllegalArgumentException("Not possible to insert cards directly (only through NOTES)");
case MODELS:
// Get input arguments
String modelName = values.getAsString(FlashCardsContract.Model.NAME);
String css = values.getAsString(FlashCardsContract.Model.CSS);
Long did = values.getAsLong(FlashCardsContract.Model.DECK_ID);
String fieldNames = values.getAsString(FlashCardsContract.Model.FIELD_NAMES);
Integer numCards = values.getAsInteger(FlashCardsContract.Model.NUM_CARDS);
Integer sortf = values.getAsInteger(FlashCardsContract.Model.SORT_FIELD_INDEX);
Integer type = values.getAsInteger(FlashCardsContract.Model.TYPE);
String latexPost = values.getAsString(FlashCardsContract.Model.LATEX_POST);
String latexPre = values.getAsString(FlashCardsContract.Model.LATEX_PRE);
// Throw exception if required fields empty
if (modelName == null || fieldNames == null || numCards == null) {
throw new IllegalArgumentException("Model name, field_names, and num_cards can't be empty");
}
if (did != null && col.getDecks().isDyn(did)) {
throw new IllegalArgumentException("Cannot set a filtered deck as default deck for a model");
}
// Create a new model
ModelManager mm = col.getModels();
Model newModel = mm.newModel(modelName);
try {
// Add the fields
String[] allFields = Utils.splitFields(fieldNames);
for (String f : allFields) {
mm.addFieldInNewModel(newModel, mm.newField(f));
}
// Add some empty card templates
for (int idx = 0; idx < numCards; idx++) {
String card_name = mContext.getResources().getString(R.string.card_n_name, idx + 1);
JSONObject t = Models.newTemplate(card_name);
t.put("qfmt", String.format("{{%s}}", allFields[0]));
String answerField = allFields[0];
if (allFields.length > 1) {
answerField = allFields[1];
}
t.put("afmt", String.format("{{FrontSide}}\\n\\n<hr id=answer>\\n\\n{{%s}}", answerField));
mm.addTemplateInNewModel(newModel, t);
}
// Add the CSS if specified
if (css != null) {
newModel.put("css", css);
}
// Add the did if specified
if (did != null) {
newModel.put("did", did);
}
if (sortf != null && sortf < allFields.length) {
newModel.put("sortf", sortf);
}
if (type != null) {
newModel.put("type", type);
}
if (latexPost != null) {
newModel.put("latexPost", latexPost);
}
if (latexPre != null) {
newModel.put("latexPre", latexPre);
}
// Add the model to collection (from this point on edits will require a full-sync)
mm.add(newModel);
col.save();
// Get the mid and return a URI
String mid = Long.toString(newModel.getLong("id"));
return Uri.withAppendedPath(FlashCardsContract.Model.CONTENT_URI, mid);
} catch (JSONException e) {
Timber.e(e, "Could not set a field of new model %s", modelName);
return null;
}
case MODELS_ID:
// Model ID is generated automatically by libanki
throw new IllegalArgumentException("Not possible to insert model with specific ID");
case MODELS_ID_TEMPLATES:
{
ModelManager models = col.getModels();
Long mid = getModelIdFromUri(uri, col);
Model existingModel = models.get(mid);
if (existingModel == null) {
throw new IllegalArgumentException("model missing: " + mid);
}
String name = values.getAsString(CardTemplate.NAME);
String qfmt = values.getAsString(CardTemplate.QUESTION_FORMAT);
String afmt = values.getAsString(CardTemplate.ANSWER_FORMAT);
String bqfmt = values.getAsString(CardTemplate.BROWSER_QUESTION_FORMAT);
String bafmt = values.getAsString(CardTemplate.BROWSER_ANSWER_FORMAT);
try {
JSONObject t = Models.newTemplate(name);
t.put("qfmt", qfmt);
t.put("afmt", afmt);
t.put("bqfmt", bqfmt);
t.put("bafmt", bafmt);
models.addTemplate(existingModel, t);
models.save(existingModel);
col.save();
return ContentUris.withAppendedId(uri, t.getInt("ord"));
} catch (ConfirmModSchemaException e) {
throw new IllegalArgumentException("Unable to add template without user requesting/accepting full-sync", e);
} catch (JSONException e) {
throw new IllegalArgumentException("Unable to get ord from new template", e);
}
}
case MODELS_ID_TEMPLATES_ID:
throw new IllegalArgumentException("Not possible to insert template with specific ORD");
case MODELS_ID_FIELDS:
{
ModelManager models = col.getModels();
long mid = getModelIdFromUri(uri, col);
Model existingModel = models.get(mid);
if (existingModel == null) {
throw new IllegalArgumentException("model missing: " + mid);
}
String name = values.getAsString(FlashCardsContract.Model.FIELD_NAME);
if (name == null) {
throw new IllegalArgumentException("field name missing for model: " + mid);
}
JSONObject field = models.newField(name);
try {
models.addField(existingModel, field);
col.save();
JSONArray flds = existingModel.getJSONArray("flds");
return ContentUris.withAppendedId(uri, flds.length() - 1);
} catch (ConfirmModSchemaException e) {
throw new IllegalArgumentException("Unable to insert field: " + name, e);
} catch (JSONException e) {
throw new IllegalArgumentException("Unable to get newly created field: " + name, e);
}
}
case SCHEDULE:
// Doesn't make sense to insert an object into the schedule table
throw new IllegalArgumentException("Not possible to perform insert operation on schedule");
case DECKS:
// Insert new deck with specified name
String deckName = values.getAsString(FlashCardsContract.Deck.DECK_NAME);
did = col.getDecks().id_for_name(deckName);
if (did != null) {
throw new IllegalArgumentException("Deck name already exists: " + deckName);
}
if (!Decks.isValidDeckName(deckName)) {
throw new IllegalArgumentException("Invalid deck name '" + deckName + "'");
}
try {
did = col.getDecks().id(deckName);
} catch (DeckRenameException filteredSubdeck) {
throw new IllegalArgumentException(filteredSubdeck.getMessage());
}
Deck deck = col.getDecks().get(did);
if (deck != null) {
try {
String deckDesc = values.getAsString(FlashCardsContract.Deck.DECK_DESC);
if (deckDesc != null) {
deck.put("desc", deckDesc);
}
} catch (JSONException e) {
Timber.e(e, "Could not set a field of new deck %s", deckName);
return null;
}
}
col.getDecks().flush();
return Uri.withAppendedPath(FlashCardsContract.Deck.CONTENT_ALL_URI, Long.toString(did));
case DECK_SELECTED:
// Can't have more than one selected deck
throw new IllegalArgumentException("Selected deck can only be queried and updated");
case DECKS_ID:
// Deck ID is generated automatically by libanki
throw new IllegalArgumentException("Not possible to insert deck with specific ID");
case MEDIA:
// contentvalue should have data and preferredFileName values
return insertMediaFile(values, col);
default:
// Unknown URI type
throw new IllegalArgumentException("uri " + uri + " is not supported");
}
}
use of com.ichi2.libanki.Models in project Anki-Android by ankidroid.
the class Finder method _findField.
private String _findField(String field, String val) {
/*
* We need two expressions to query the cards: One that will use JAVA REGEX syntax and another
* that should use SQLITE LIKE clause syntax.
*/
String sqlVal = val.replace("%", // For SQLITE, we escape all % signs
"\\%").replace("*", // And then convert the * into non-escaped % signs
"%");
/*
* The following three lines make sure that only _ and * are valid wildcards.
* Any other characters are enclosed inside the \Q \E markers, which force
* all meta-characters in between them to lose their special meaning
*/
String javaVal = val.replace("_", "\\E.\\Q").replace("*", "\\E.*\\Q");
/*
* For the pattern, we use the javaVal expression that uses JAVA REGEX syntax
*/
Pattern pattern = Pattern.compile("\\Q" + javaVal + "\\E", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
// find models that have that field
Map<Long, Object[]> mods = HashUtil.HashMapInit(mCol.getModels().count());
for (JSONObject m : mCol.getModels().all()) {
JSONArray flds = m.getJSONArray("flds");
for (JSONObject f : flds.jsonObjectIterable()) {
String fieldName = f.getString("name");
fieldName = Normalizer.normalize(fieldName, Normalizer.Form.NFC);
if (fieldName.equalsIgnoreCase(field)) {
mods.put(m.getLong("id"), new Object[] { m, f.getInt("ord") });
}
}
}
if (mods.isEmpty()) {
// nothing has that field
return null;
}
LinkedList<Long> nids = new LinkedList<>();
try (Cursor cur = mCol.getDb().query("select id, mid, flds from notes where mid in " + Utils.ids2str(new LinkedList<>(mods.keySet())) + " and flds like ? escape '\\'", "%" + sqlVal + "%")) {
while (cur.moveToNext()) {
String[] flds = Utils.splitFields(cur.getString(2));
int ord = (Integer) mods.get(cur.getLong(1))[1];
String strg = flds[ord];
if (pattern.matcher(strg).matches()) {
nids.add(cur.getLong(0));
}
}
}
if (nids.isEmpty()) {
return "0";
}
return "n.id in " + Utils.ids2str(nids);
}
use of com.ichi2.libanki.Models in project Anki-Android by ankidroid.
the class Storage method _upgradeClozeModel.
private static void _upgradeClozeModel(Collection col, Model m) throws ConfirmModSchemaException {
m.put("type", Consts.MODEL_CLOZE);
// convert first template
JSONObject t = m.getJSONArray("tmpls").getJSONObject(0);
for (String type : new String[] { "qfmt", "afmt" }) {
// noinspection RegExpRedundantEscape // In Android, } should be escaped
t.put(type, t.getString(type).replaceAll("\\{\\{cloze:1:(.+?)\\}\\}", "{{cloze:$1}}"));
}
t.put("name", "Cloze");
// delete non-cloze cards for the model
JSONArray tmpls = m.getJSONArray("tmpls");
ArrayList<JSONObject> rem = new ArrayList<>();
for (JSONObject ta : tmpls.jsonObjectIterable()) {
if (!ta.getString("afmt").contains("{{cloze:")) {
rem.add(ta);
}
}
for (JSONObject r : rem) {
col.getModels().remTemplate(m, r);
}
JSONArray newTmpls = new JSONArray();
newTmpls.put(tmpls.getJSONObject(0));
m.put("tmpls", newTmpls);
Models._updateTemplOrds(m);
col.getModels().save(m);
}
Aggregations