diff --git a/Android app/app/build.gradle b/Android app/app/build.gradle index a4a15bd..bfda15e 100755 --- a/Android app/app/build.gradle +++ b/Android app/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.schlmgr" minSdkVersion 19 targetSdkVersion 30 - versionCode 52 - versionName '1.5.2' + versionCode 53 + versionName '1.6.0' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" signingConfig signingConfigs.debug } diff --git a/Android app/app/libs/SMLib.jar b/Android app/app/libs/SMLib.jar index d629cc6..396917a 100644 Binary files a/Android app/app/libs/SMLib.jar and b/Android app/app/libs/SMLib.jar differ diff --git a/Android app/app/libs/SMLib.zip b/Android app/app/libs/SMLib.zip index 0972211..4311756 100644 Binary files a/Android app/app/libs/SMLib.zip and b/Android app/app/libs/SMLib.zip differ diff --git a/Android app/app/src/main/java/com/schlmgr/gui/AndroidIOSystem.java b/Android app/app/src/main/java/com/schlmgr/gui/AndroidIOSystem.java index 70a1ed6..ae29681 100755 --- a/Android app/app/src/main/java/com/schlmgr/gui/AndroidIOSystem.java +++ b/Android app/app/src/main/java/com/schlmgr/gui/AndroidIOSystem.java @@ -241,8 +241,9 @@ public GeneralPath getDefaultSubjectsDir() { } @Override - public OutputStream outputInternal(GeneralPath file) throws IOException { - return CONTEXT.openFileOutput(file.getName(), Context.MODE_PRIVATE); + public OutputStream outputInternal(GeneralPath file, boolean append) throws IOException { + return CONTEXT.openFileOutput(file.getName(), + append ? Context.MODE_APPEND : Context.MODE_PRIVATE); } @Override diff --git a/Android app/app/src/main/java/com/schlmgr/gui/ExplorerStuff.java b/Android app/app/src/main/java/com/schlmgr/gui/ExplorerStuff.java index 3270cb6..9c1ddf7 100755 --- a/Android app/app/src/main/java/com/schlmgr/gui/ExplorerStuff.java +++ b/Android app/app/src/main/java/com/schlmgr/gui/ExplorerStuff.java @@ -75,10 +75,10 @@ public interface BackUpdater { } public ExplorerStuff(OnItemActionListener l, Content onItemClick, Runnable oss, Runnable onCheck, - ViewState vs, BackLog bl, Context c, HorizontalScrollView hsv, - RecyclerView rv, LinearLayout path, ScrollView infoScroll, TextView info, - SearchView searchView, View touchOutside, AppBarLayout searchCollapser, - BackUpdater bu) { + ViewState vs, BackLog bl, Context c, HorizontalScrollView hsv, + RecyclerView rv, LinearLayout path, ScrollView infoScroll, TextView info, + SearchView searchView, View touchOutside, AppBarLayout searchCollapser, + BackUpdater bu) { this.itemListener = l; this.bu = bu; content = onItemClick; diff --git a/Android app/app/src/main/java/com/schlmgr/gui/UriPath.java b/Android app/app/src/main/java/com/schlmgr/gui/UriPath.java index 1ac6b7d..3faf64f 100644 --- a/Android app/app/src/main/java/com/schlmgr/gui/UriPath.java +++ b/Android app/app/src/main/java/com/schlmgr/gui/UriPath.java @@ -47,7 +47,6 @@ public UriPath(Uri uri) { } public UriPath(Uri uri, boolean internal) { - System.out.println(uri.toString()); this.uri = uri; original = uri.toString(); if (uri.toString().startsWith("file://")) @@ -75,8 +74,8 @@ public DocumentFile getDocumentFile() { } @Override - public OutputStream createOutputStream() throws IOException { - return CONTEXT.getContentResolver().openOutputStream(uri); + public OutputStream createOutputStream(boolean append) throws IOException { + return CONTEXT.getContentResolver().openOutputStream(uri, "wa"); } @Override diff --git a/Android app/app/src/main/java/com/schlmgr/gui/fragments/AboutFragment.java b/Android app/app/src/main/java/com/schlmgr/gui/fragments/AboutFragment.java index cf360a1..03ca5a0 100755 --- a/Android app/app/src/main/java/com/schlmgr/gui/fragments/AboutFragment.java +++ b/Android app/app/src/main/java/com/schlmgr/gui/fragments/AboutFragment.java @@ -87,12 +87,13 @@ public class AboutFragment extends Fragment implements ControlListener { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_about, container, false); - activity.getSupportActionBar().setTitle(activity.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME); + activity.getSupportActionBar().setTitle(activity.getString(R.string.app_name) + " v" + BuildConfig.VERSION_NAME); new Thread(() -> { versions_release.setTgl(root); versions_beta.setTgl(root); RecyclerView rvRelease = (RecyclerView) versions_release.toToggle; String[][] releases = { + {"1.6.0", "- reworked and improved simple word exports, now more robust"}, {"1.5.2", "- altered filesystem usage to handle Android 10+ Storage access framework"}, {"1.5.0", "- working system for moving and referencing objects with recursion protection"}, {"1.4.3", "- fixed some library issues with missing values"}, diff --git a/Android app/app/src/main/java/com/schlmgr/gui/fragments/MainFragment.java b/Android app/app/src/main/java/com/schlmgr/gui/fragments/MainFragment.java index a9d6596..f40c40d 100755 --- a/Android app/app/src/main/java/com/schlmgr/gui/fragments/MainFragment.java +++ b/Android app/app/src/main/java/com/schlmgr/gui/fragments/MainFragment.java @@ -70,6 +70,7 @@ import static IOSystem.Formatter.defaultReacts; import static IOSystem.Formatter.getIOSystem; +import static com.schlmgr.gui.Controller.CONTEXT; import static com.schlmgr.gui.Controller.activity; import static com.schlmgr.gui.Controller.dp; import static com.schlmgr.gui.CurrentData.backLog; @@ -157,30 +158,6 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, } new Thread(() -> { - delete.setOnClickListener(v -> { - if (((SearchAdapter) backLog.adapter).selected > 0) - new ContinuePopup(getString(R.string.continue_delete), () -> { - for (int i = VS.contentAdapter.list.size() - 1; i >= 0; i--) { - HierarchyItemModel him = VS.contentAdapter.list.get(i); - if (him.isSelected()) { - if (backLog.path.isEmpty()) him.bd.destroy(null); - else { - him.parent.removeChild((Container) (him instanceof SearchItemModel ? - ((SearchItemModel) him).path.get(-2) - : backLog.path.get(-2)), him.bd); - him.bd.destroy(him.parent); - } - VS.contentAdapter.list.remove(i); - } - } - if (!backLog.path.isEmpty()) CurrentData.save(backLog.path); - root.post(() -> { - VS.contentAdapter.notifyDataSetChanged(); - VS.contentAdapter.selected = -1; - selectOpts.setVisibility(View.GONE); - }); - }); - }); reference.setOnClickListener(v -> { int search = 0; EasyList list = null; @@ -305,6 +282,29 @@ public View onInclude(LayoutInflater li, CreatorPopup cp) { })); } }); + delete.setOnClickListener(v -> { + if (((SearchAdapter) backLog.adapter).selected > 0) + new ContinuePopup(getString(R.string.continue_delete), () -> root.post(() -> { + boolean left = false; + for (int i = VS.contentAdapter.list.size() - 1; i >= 0; i--) { + HierarchyItemModel him = VS.contentAdapter.list.get(i); + if (him.isSelected()) { + if (him.bd.destroy(him.parent)) { + VS.contentAdapter.list.remove(i); + VS.contentAdapter.notifyItemRemoved(i); + } else left = true; + } + } + if (!backLog.path.isEmpty()) CurrentData.save(backLog.path); + if (VS.contentAdapter instanceof HierarchyAdapter && !backLog.path.isEmpty()) + es.setInfo(backLog.path.get(-1), (Container) backLog.path.get(-2)); + if (!left) { + VS.contentAdapter.selected = -1; + es.rv.postDelayed(() -> VS.contentAdapter.notifyDataSetChanged(), 200); + selectOpts.setVisibility(View.GONE); + } else Toast.makeText(CONTEXT, R.string.popup_delete_fail, Toast.LENGTH_SHORT).show(); + })); + }); if (backLog.adapter instanceof SearchAdapter && VS.contentAdapter.selected > -1) { selectOpts.setVisibility(View.VISIBLE); for (HierarchyItemModel him : (List) backLog.adapter.list) @@ -356,12 +356,6 @@ private void move(boolean ref) { if (him.isSelected()) VS.pasteData.src.add(him); for (BasicData bd : backLog.path) VS.pasteData.srcPath.add((Container) bd); paste.setOnClickListener(v -> { - /*if (backLog.path.size() == VS.pasteData.srcPath.size()) { - int i = 0; - boolean ok = false; - for (BasicData bd : backLog.path) if (ok = bd != VS.pasteData.srcPath.get(i++)) break; - if (!ok) return; - }*/ Container npp = (Container) backLog.path.get(-2); Container np = (Container) backLog.path.get(-1); boolean searchNow = VS.contentAdapter.search; @@ -465,14 +459,14 @@ private void setVisibleOpts() { */ private void tglEnabled(TextView tv, boolean enabled) { if (tv.isEnabled() == enabled) return; - if (tv == delete) - tv.setCompoundDrawables(null, enabled ? icDelete : icDelete_disabled, null, null); - else if (tv == reference) + if (tv == reference) tv.setCompoundDrawables(null, enabled ? icReference : icReference_disabled, null, null); else if (tv == cut) tv.setCompoundDrawables(null, enabled ? icCut : icCut_disabled, null, null); else if (tv == edit) tv.setCompoundDrawables(null, enabled ? icEdit : icEdit_disabled, null, null); + else if (tv == delete) + tv.setCompoundDrawables(null, enabled ? icDelete : icDelete_disabled, null, null); else if (tv == paste) tv.setCompoundDrawables(null, enabled ? icPaste : icPaste_disabled, null, null); tv.setTextColor(enabled ? 0xFFFFFFFF : 0x66FFFFFF); @@ -543,6 +537,8 @@ this, convert(new ArrayList<>(MainChapter.ELEMENTS), null), Controller.activity.getSupportActionBar().setTitle(bd.toString()); } if (container) { + if (VS.contentAdapter.list != null) + for (HierarchyItemModel him : VS.contentAdapter.list) him.setSelected(false); es.rv.setAdapter(backLog.adapter = VS.contentAdapter = new HierarchyAdapter(es.rv, this, convert(((Container) bd).getChildren(parent), (Container) bd), this::setVisibleOpts)); @@ -892,10 +888,18 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { case WORD_READ: try { List currentPath = (List) backLog.path.clone(); - defaultReacts.get(SimpleReader.class + ":success").react( - SimpleReader.simpleLoad(new UriPath(data.getData()).load(), - (Container) backLog.path.get(-1), - (Container) backLog.path.get(-2), 0, -1, -1)); + Container self = (Container) backLog.path.get(-1), + par = (Container) backLog.path.get(-2); + SimpleReader content = new SimpleReader(new UriPath(data.getData()).load(), self, par); + if (backLog.adapter instanceof HierarchyAdapter) { + HierarchyAdapter ha = (HierarchyAdapter) backLog.adapter; + int old = ha.list.size(); + int pos = old; + for (BasicData bd : content.added) + ha.addItem(new HierarchyItemModel(bd, self, pos++)); + es.rv.post(() -> es.setInfo(self, par)); + } + defaultReacts.get(SimpleReader.class + ":success").react(content.result); CurrentData.save(currentPath); } catch (Exception e) { if (e instanceof IllegalArgumentException) return; @@ -904,17 +908,8 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } break; case WORD_WRITE: - try (OutputStreamWriter osw = new OutputStreamWriter( - activity.getContentResolver().openOutputStream(data.getData()), - StandardCharsets.UTF_8)) { - osw.append(SimpleWriter.writeChapter(new StringBuilder(), - (Container) backLog.path.get(-2), (Container) backLog.path.get(-1))); - Formatter.defaultReacts.get(SimpleWriter.class + ":success") - .react(backLog.path.get(-1).getName()); - } catch (Exception e) { - Formatter.defaultReacts.get(ContainerFile.class + ":save") - .react(e, data.getData(), backLog.path.get(-1)); - } + new SimpleWriter(new UriPath(data.getData()), new Container[]{ + (Container) backLog.path.get(-1), (Container) backLog.path.get(-2)}); break; case IMAGE_PICK: defaultReacts.get("NotifyNewImage") diff --git a/Android app/app/src/main/java/com/schlmgr/gui/fragments/SettingsFragment.java b/Android app/app/src/main/java/com/schlmgr/gui/fragments/SettingsFragment.java index 0181d32..4d02b68 100755 --- a/Android app/app/src/main/java/com/schlmgr/gui/fragments/SettingsFragment.java +++ b/Android app/app/src/main/java/com/schlmgr/gui/fragments/SettingsFragment.java @@ -6,6 +6,7 @@ import android.view.ViewGroup; import android.widget.CompoundButton; import android.widget.EditText; +import android.widget.NumberPicker; import android.widget.TextView.BufferType; import androidx.annotation.NonNull; @@ -18,15 +19,17 @@ import com.schlmgr.gui.list.HierarchyItemModel; import IOSystem.Formatter; +import IOSystem.SimpleWriter; import testing.Test; public class SettingsFragment extends Fragment implements ControlListener { + private NumberPicker wordSplit; private EditText amount; private EditText time; public View onCreateView(@NonNull LayoutInflater inflater, - ViewGroup container, Bundle savedInstanceState) { + ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_settings, container, false); CompoundButton parse = root.findViewById(R.id.setts_him_parse); @@ -49,6 +52,12 @@ public View onCreateView(@NonNull LayoutInflater inflater, desc.setChecked(HierarchyItemModel.show_desc); desc.setOnClickListener(v -> HierarchyItemModel.setShowDesc(desc.isChecked())); + wordSplit = root.findViewById(R.id.setts_word_splitter); + wordSplit.setMinValue(0); + wordSplit.setMaxValue(3); + wordSplit.setDisplayedValues(new String[]{"';'", "'='", "' = '", "' → '"}); + wordSplit.setValue(getWordSplit(SimpleWriter.getWordSplitter())); + amount = root.findViewById(R.id.setts_test_amount); amount.setText("" + Test.getAmount(), BufferType.EDITABLE); amount.setOnFocusChangeListener((v, hasFocus) -> { @@ -80,10 +89,39 @@ public View onCreateView(@NonNull LayoutInflater inflater, return root; } + public static int getWordSplit(String wordSplitter) { + switch (wordSplitter) { + case "=": + return 1; + case " = ": + return 2; + case " → ": + return 3; + case ";": + default: + return 0; + } + } + + public static String getWordSplit(int wordSplitter) { + switch (wordSplitter) { + case 1: + return "="; + case 2: + return " = "; + case 3: + return " → "; + case 0: + default: + return ";"; + } + } + @Override public void onDestroy() { Test.setAmount(Integer.parseInt(amount.getText().toString())); Test.setDefaultTime(Integer.parseInt(time.getText().toString())); + SimpleWriter.setWordSplitter(getWordSplit(wordSplit.getValue())); super.onDestroy(); } diff --git a/Android app/app/src/main/res/layout/fragment_main.xml b/Android app/app/src/main/res/layout/fragment_main.xml index 0f6a07f..2f2ce5f 100755 --- a/Android app/app/src/main/res/layout/fragment_main.xml +++ b/Android app/app/src/main/res/layout/fragment_main.xml @@ -2,8 +2,8 @@ + android:background="@color/colorPrimaryBg" + android:orientation="vertical"> - - + + + + + + + + + + + + android:descendantFocusability="blocksDescendants" /> Import slovíček Slovíčka lze do předmětu hromadně přidat pomocí souboru, který vyberete po kliknutí na možnost \'Importovat slovíčka\' v menu pro více možností. \nSoubor může obsahovat kapitoly a slovíčka. - \nPro zápis více slovíček se stejným překladem (a naopak) oddělte slovíčka zpětným lomítkem \. - \nSlovíčka a jejich překlady oddělte středníkem ;. - \nKapitoly pište na nový řádek název, na další \'{\', jako začátek kapitoly a pro zakončení kapitoly na další řádek \'}\'. - \nKaždou položku pište na nový řádek, nepřidávejte předsazení. - \nPište soubor v následujícím formátu: - \nnázev kapitoly - \n{ - \nslovíčko;překlad - \ndalší kapitola - \n{ - \n… - \n} - \ndalší kapitola - \n{ - \n… - \n} - \nslovíčko 1\slovíčko 2;překlad pro obě + \nPoznámky k položkám pište ve tvaru \'\\[poznámky k položce\\]\'. + \nSlovíčka a jejich překlady oddělte pomocí \';\', \'=\', nebo \'→\'. + \nPro zápis více slovíček se stejným překladem (a naopak) oddělte slovíčka zpětným lomítkem \\. + \nKapitoly začínají řádkem ve tvaru \'název kapitoly \\[případné poznámky\\] {\', kapitolu ukončíte pomocí \'}\' na samostatném řádku. Přímo před otevřenou závorku můžete přidat \'§\' pro uložení kapitoly do vlastního souboru. + \nKaždou položku pište na nový řádek, odsazení a mezery mezi jednotlivými položkami jsou libovolné. + \nMožný příkladný formát: + \n\nkapitola v souboru §{ + \n\tslovíčko 1;překlad 1 + \n\tslovíčko 2=překlad 2 + \n\tnázev podkapitoly \\[poznámky k podkapitole\\] { + \n\t\tslovíčko 3 \\[poznámky\\] = překlad 3 + \n\t\tslovíčko 4\\slovíčko 5;překlad 4 + \n\t\tslovíčko 6 \\[poznámka\\] \\ slovíčko 7 → překlad 5 + \n\t} + \n\tslovíčko 7;překlad 1 \\[poznámka překladu\\]\\překlad 6 \n} Vyhledávání Vyhledávání je umožňěno ve všech kapitolách a předmětech. @@ -228,6 +226,7 @@ Vložit Špatná syntax regexu: Kopírovat + Odstranění neúspěšné Stiskněte ještě jednou pro ukončení Zadat klíčové slovo Zvolit @@ -254,6 +253,7 @@ Barva textu Barva záhlaví Hlavní barvy + Sekvence dělící slova od překladů při exportu Seřadit abecedně ▼ abecedně ▲ diff --git a/Android app/app/src/main/res/values/strings.xml b/Android app/app/src/main/res/values/strings.xml index 72f8625..44d7283 100755 --- a/Android app/app/src/main/res/values/strings.xml +++ b/Android app/app/src/main/res/values/strings.xml @@ -90,26 +90,24 @@ \nAny pictures or file chapters included in the loaded file have to be copied manually. \nIt is highly recommended not to import subject files. Words exporting - Export words by opening a chapter which content you want to export and select \'Export words\' option in the more options menu. - \nThan just select the file you want to add the current word content in. - \nThe exported words can be imported again without loss. + Export words by opening a chapter which content you want to export and select \'Export words\' option in the more options menu. Than just select the file you want to add the current word content in. The exported words can be imported again without loss. Words importing - You can import words from a file, it can be also structured in a simpler hierarchy. + You can import words from a file, it can be also structured in a simpler hierarchy. The file can contain chapters and words. + \nDescription to individual items can be written directly behind the item in format like \'\\[description\\]\'. + \nWords and translates can be separated using \';\', \'=\', or \'→\'. \nWords with same translate are separated by a backward slash \'\\\'. - \nWrite the file for import in the following format: - \nname of a chapter - \n{ - \na word;its translate - \nanother word;translate - \nsubchapter\'s name - \n{ - \n… - \n} - \nanother subchapter - \n{ - \n… - \n} - \nword 1\word 2;their translate + \nChapters begin on a separate line in the following format \'chapter name \\[optional description\\] {\', end them with a single \'}\' on a separate line. For saving the chapter into an etra file, use \'§\' directly before \'{\'. + \nEvery item should be written on a new line, spacing and indentation between them is ignored - meant only for better readability. + \nPossible format example: + \n\nchapter-in-file 1 §{ + \n\tword 1;translate 1 + \n\tword 2=translate 2 + \n\tsubchapter name \\[subchapter description\\] { + \n\t\tword 3 \\[description\\] = translate 3 + \n\t\tword 4\\word 5;translate 4 + \n\t\tword 6 \\[description\\] \\ word 7 → translate 5 + \n\t} + \n\tword 7;translate 1 \\[description\\]\\translate 6 \n} Searching Searching is enabled in all chapters and subjects. @@ -221,6 +219,7 @@ Paste Invalid regex syntax: Copy + Deletion failed Press again to exit Input Keyword Select @@ -247,6 +246,7 @@ Text color Header color Main colors + Sequence splitting words from translates when exporting Sort alphabet ▼ alphabet ▲ diff --git a/Android app/build.gradle b/Android app/build.gradle index 3bab362..36f269d 100755 --- a/Android app/build.gradle +++ b/Android app/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.0.0' + classpath 'com.android.tools.build:gradle:7.0.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/Library/nbproject/private/private.xml b/Library/nbproject/private/private.xml index e020e0b..440a145 100644 --- a/Library/nbproject/private/private.xml +++ b/Library/nbproject/private/private.xml @@ -3,8 +3,7 @@ - file:/home/kepis/Documents/SchoolManager/Library/src/IOSystem/SimpleReader.java - file:/home/kepis/Documents/SchoolManager/Library/src/IOSystem/Formatter.java + file:/home/kepis/Documents/SchoolManager/Library/src/IOSystem/SimpleWriter.java diff --git a/Library/src/IOSystem/FilePath.java b/Library/src/IOSystem/FilePath.java index e4ef5f3..ef32bbe 100644 --- a/Library/src/IOSystem/FilePath.java +++ b/Library/src/IOSystem/FilePath.java @@ -60,8 +60,9 @@ public String getName() { } @Override - public OutputStream createOutputStream() throws IOException { - return internal ? Formatter.getIOSystem().outputInternal(this) : new FileOutputStream(file); + public OutputStream createOutputStream(boolean append) throws IOException { + return internal ? Formatter.getIOSystem().outputInternal(this, append) + : new FileOutputStream(file, append); } @Override diff --git a/Library/src/IOSystem/Formatter.java b/Library/src/IOSystem/Formatter.java index 7fd355e..a491acb 100644 --- a/Library/src/IOSystem/Formatter.java +++ b/Library/src/IOSystem/Formatter.java @@ -27,7 +27,7 @@ * * @author Josef Litoš */ -public class Formatter { +public final class Formatter { /** * Default {@link Reactioner object} made for answering on various actions, keys should be in @@ -198,6 +198,11 @@ protected IOSystem(Object setts) { } else { settings.put("testAmount", 10); } + if ((value = settings.get("exportWordSplit")) != null) { + SimpleWriter.setWordSplitter((String) value); + } else { + settings.put("exportWordSplit", 1); + } setDefaults(false); } catch (Exception e) { defaultReacts.get(Formatter.class + ":newSrcDir").react(e, setts); @@ -205,7 +210,7 @@ protected IOSystem(Object setts) { } } if (subjDir == null) { - settings.put("subjdir", getDefaultSubjectsDir().getOriginalName()); + settings.put("subjdir", (subjDir = getDefaultSubjectsDir()).getOriginalName()); settings.put("defaultTestTime", 180); settings.put("isClever", true); settings.put("testAmount", 10); @@ -251,7 +256,11 @@ protected GeneralPath changeDir(GeneralPath newDir) { return subjDir = newDir; } - public abstract OutputStream outputInternal(GeneralPath file) throws IOException; + public OutputStream outputInternal(GeneralPath file) throws IOException { + return outputInternal(file, false); + } + + public abstract OutputStream outputInternal(GeneralPath file, boolean append) throws IOException; public abstract InputStream inputInternal(GeneralPath file) throws IOException; @@ -262,8 +271,12 @@ public static interface GeneralPath { public String getName(); public default void save(String content) { + save(content, false); + } + + public default void save(String content, boolean append) { try ( OutputStreamWriter osw - = new OutputStreamWriter(createOutputStream(), StandardCharsets.UTF_8)) { + = new OutputStreamWriter(createOutputStream(append), StandardCharsets.UTF_8)) { osw.write(content); } catch (IOException e) { defaultReacts.get(ContainerFile.class + ":save") @@ -304,7 +317,11 @@ public default Object serialize() { } } - public OutputStream createOutputStream() throws IOException; + public default OutputStream createOutputStream() throws IOException { + return createOutputStream(false); + } + + public OutputStream createOutputStream(boolean append) throws IOException; public InputStream createInputStream() throws IOException; diff --git a/Library/src/IOSystem/SimpleReader.java b/Library/src/IOSystem/SimpleReader.java index 1c11ea9..e1ac5e6 100644 --- a/Library/src/IOSystem/SimpleReader.java +++ b/Library/src/IOSystem/SimpleReader.java @@ -1,17 +1,14 @@ package IOSystem; -import java.util.ArrayList; -import java.util.Arrays; +import IOSystem.Formatter.Data; import java.util.LinkedList; import java.util.List; import objects.Chapter; -import objects.MainChapter; import objects.SaveChapter; import objects.Word; import objects.templates.Container; - -import static IOSystem.Formatter.Data; +import objects.templates.BasicData; /** * Creates hierarchy object structure out of {@link Word words} inside {@link SaveChapter} @@ -30,268 +27,188 @@ * * @author Josef Litoš */ -public class SimpleReader { - - /** - * @param saveTo where to save the loaded data - * @param source where are the data to be read - * @param startIndex the first read chapter (inclusive) - * @param endIndex the last chapter to be read (exclusive), put -1 for no limit - * @param minWords how many words wil be created before the end of loading, put -1 - * for no limit - * @return amount of created chapters, words, and translates - */ - static int[] loadWords(Container saveTo, Container parent, SimpleChapter source, - int startIndex, int endIndex, int minWords) { - int[] ret = {0, 0, 0}; - if (minWords == -1) minWords = Integer.MAX_VALUE; - MainChapter mch = saveTo.getIdentifier(); - Data bd = new Data(null, mch).addPar(saveTo); - if (source.chaps != null) { - if (endIndex == -1) endIndex = source.chaps.length; - startIndex--; - endIndex--; - while (startIndex++ < endIndex && ret[1] + source.lines.length < minWords) { - bd.name = source.chaps[startIndex].name; - Container ch = source.chaps[startIndex].sch - ? SaveChapter.mkElement(bd) : new Chapter(bd); - saveTo.putChild(parent, ch); - int[] x = loadWords(ch, saveTo, source.chaps[startIndex], 0, -1, minWords); - ret[0]++; - ret[1] += x[1]; - ret[2] += x[2]; - } +public final class SimpleReader { + + public final int[] result = {0, 0, 0}; + public final List added = new LinkedList<>(); + final String src; + int i = -1; + int lineStart = 0, line = 1; + + public SimpleReader(String source, Container container, Container parent) { + src = source; + try { + loadContent(added, container, parent); + } catch (IllegalArgumentException iae) { + for (BasicData bd : added) bd.destroy(container); + throw iae; } - for (SimpleLine sl : source.lines) { - ArrayList bds = new ArrayList<>(sl.words[1].length); - for (String trl : sl.words[1]) bds.add(new Data(trl, mch).addPar(saveTo)); - for (String s : sl.words[0]) { - bd.name = s; - saveTo.putChild(parent, Word.mkElement(bd, new ArrayList<>(bds))); - } - ret[1] += sl.words[0].length; - ret[2] += sl.words[1].length; - } - return ret; - } - - /** - * Loads everything to chapters in the specified name format. - * - * @param source where to read the data from - * @param parent the object where all created chapters and their content will be - * stored - * @param prevPar parent of the given param {@code parent} - * @param startIndex the first read chapter (inclusive) - * @param endIndex the last chapter to be read (exclusive), put -1 for no limit - * @param wordCount amount of words read before starting another - * {@link SaveChapter} (after the last chapter in the boundary is - * loaded) - * @param startNum increasing number used to name all the created chapters - * @param psFix prefix and suffix for the names of created chapters with the - * current increasing {@code startNum} value - * @param b if the containers should save into separate files - * @return the final amount of read chapters and of all created words - */ - public static int[] sortLoad(String source, Container parent, - Container prevPar, int startIndex, int endIndex, - int wordCount, int startNum, String[] psFix, boolean b) { - MainChapter mch = parent.getIdentifier(); - int[] res = {0, 0, 0}, i; - SimpleChapter sch = getContent(source); - if (sch.chaps != null) - if (endIndex == -1 || endIndex > sch.chaps.length) endIndex = sch.chaps.length; - do { - Data bd = new Data(psFix[0] + startNum++ + psFix[1], mch).addPar(parent); - Container c = b ? SaveChapter.mkElement(bd) : new Chapter(bd); - i = loadWords(c, parent, sch, startIndex, -1, wordCount); - res[0] += i[0]; - startIndex += i[0]; - res[1] += i[1]; - res[2] += i[2]; - if (i[1] <= 0) break; - else parent.putChild(prevPar, c); - } while (startIndex < endIndex); - return res; - } - - /** - * Loads everything from the given file based on the given parameters. - * - * @param source where to read the data from - * @param parent the object where all created chapters and their content will be - * stored - * @param prevPar parent of the given param {@code parent} - * @param startIndex the first read chapter (inclusive) - * @param endIndex the last chapter to be read (exclusive), put -1 for no limit - * @param wordCount amount of words read before the end, put -1 for no limit - * @return the final amount of read chapters and of all created words - */ - public static int[] simpleLoad(String source, Container parent, Container prevPar, - int startIndex, int endIndex, int wordCount) { - return loadWords(parent, prevPar, - getContent(source), startIndex, endIndex, wordCount); } - - /** - * Creates hierarchy of words and chapters. - * Used for converting simple text to database format. - * - * @param source where to get the content from - * @return the created simple hierarchy - */ - private static SimpleChapter getContent(String source) { - String[] lines = source.split("\n"); - return new SimpleChapter( - null, new Lines(0, Arrays.copyOf(lines, lines.length + 1))); - } - - private static class Lines { - - int i; - final int length; - final String[] str; - - Lines(int index, String[] lines) { - i = index; - str = lines; - length = lines.length; - } - } - - /** - * Stores chapters and word-lines. Part of the simple hierarchy loading. - */ - private static class SimpleChapter { - - SimpleChapter[] chaps; - SimpleLine[] lines; - String name; - boolean sch; - - SimpleChapter(String name, Lines lines) { - this.name = name; - sch = false; - LinkedList sLines = new LinkedList<>(); - LinkedList schs = new LinkedList<>(); - sorter: - for (int i = lines.i; i < lines.length - 1; i++) { - if (lines.str[i].length() <= 2) {//2 for occasional char codepoint-13 - if (!lines.str[i].isEmpty() && lines.str[i].charAt(0) == '}') { - lines.i = i; - break sorter; - } - } else {//every word line has at least 3 chars - word, ';', translate - String[] names = new String[2]; - String line = lines.str[i]; - boolean first = true; - StringBuilder sb = new StringBuilder(); - for (int j = 0; j < line.length(); j++) { - switch (line.charAt(j)) { - case '\\': - switch (line.charAt(j + 1)) { - case ';': - j++; - sb.append(';'); - break; - case '\\': - case '/': - case '(': - case ')': - j++; - sb.append('\\'); - default: - sb.append(line.charAt(j)); - } - break; - case ';': - names[first ? 0 : 1] = sb.toString(); - if (first) { - first = false; - sb.setLength(0); - } else j = line.length(); + + private void loadContent(List carrier, Container self, Container parent) { + StringBuilder sb = new StringBuilder(); + List words = new LinkedList<>(); + List translates = new LinkedList<>(); + List current = words; + char ch; + while (++i < src.length()) { + switch (ch = src.charAt(i)) { + case '\\': + if (i + 1 >= src.length()) break; + char ch2 = src.charAt(++i); + switch (ch2) { + case '[': + if (sb.length() > 0) current.add(new Data(sb.toString().trim(), + self.getIdentifier()).addPar(self).addDesc(getDescription())); + else throw report(self.getName() + " - line " + line + ":\n'" + + src.substring(lineStart, i + 1) + "'\nExpected text. Got '\\['."); + sb.setLength(0); break; - default: - sb.append(line.charAt(j)); - } - } - if (names[0] == null && sb.length() == 0) { - Formatter.defaultReacts.get(SimpleReader.class + ":fail").react(line); - throw new IllegalArgumentException(); - } else if (sb.length() > 0 && lines.str[i + 1].startsWith("{")) { - lines.i = i + 2; - schs.add(new SimpleChapter(sb.toString(), lines)); - i = lines.i; - } else { - names[1] = sb.toString(); - sLines.add(new SimpleLine(names)); + case '\\': + case '/': + case '(': + case ')': + sb.append('\\'); + case ';': + case '=': + case '→': + sb.append(ch2); + break; + default: + if (sb.length() > 0) { + current.add(new Data(sb.toString().trim(), self.getIdentifier()).addPar(self)); + sb.setLength(0); + } else if (current.isEmpty()) throw report(self.getName() + " - line " + line + ":\n'" + + src.substring(lineStart, i + 1) + "'\nExpected text. Got '\\'."); + if (current == words) result[1]++; + else result[2]++; + sb.append(ch2); } - } - } - this.lines = sLines.toArray(new SimpleLine[sLines.size()]); - if (!schs.isEmpty()) { - chaps = schs.toArray(new SimpleChapter[schs.size()]); - if (schs.size() > 10) { - sch = true; - return; - } - int words = 0; - for (SimpleChapter sc : schs) { - if (!sc.sch) words += sc.lines.length; - if (words > 80) { - sch = true; + break; + case ';': + case '=': + case '→': + if (sb.length() > 0) { + current.add(new Data(sb.toString().trim(), self.getIdentifier()).addPar(self)); + sb.setLength(0); + } + if (current == words && !words.isEmpty()) { + result[1]++; + current = translates; break; + } else throw report(self.getName() + " - line " + line + ":\n'" + + src.substring(lineStart, i + 1) + "'\nExpected '\\[', '\\n' or text. Got '" + ch + "'."); + case '\r': + i++; + case '\n': + if (sb.length() > 0) { + current.add(new Data(sb.toString().trim(), self.getIdentifier()).addPar(self)); + sb.setLength(0); } - } + if (current == translates && !translates.isEmpty()) { + result[2]++; + for (Data word : words) { + Word w = Word.mkElement(word, translates); + self.putChild(parent, w); + if (carrier != null) carrier.add(w); + } + } else throw report(self.getName() + " - line " + line + ":\n'" + + src.substring(lineStart, i) + "'\nExpected ';' and text, or '{'. Got '\\n'."); + words.clear(); + translates.clear(); + current = words; + lineStart = i + 1; + line++; + break; + case '}': + if (src.substring(lineStart, i).trim().isEmpty()) { + while (++i < src.length() && src.charAt(i) != '\n'); + lineStart = i + 1; + line++; + return; + } + sb.append(ch); + break; + case '{': + if (i + 1 < src.length() && src.charAt(i + 1) == '\n' && current != translates) { + lineStart = ++i; + line++; + Data data; + if (words.isEmpty()) { + if (sb.charAt(sb.length() - 1) == '§') sb.setLength(sb.length() - 1); + data = new Data(sb.toString().trim(), self.getIdentifier()).addPar(self); + } else data = words.get(0); + words.clear(); + sb.setLength(0); + Container child; + if (src.charAt(i - 2) == '§') self.putChild(parent, child = SaveChapter.mkElement(data)); + else self.putChild(parent, child = new Chapter(data)); + if (carrier != null) carrier.add(child); + loadContent(null, child, self); + result[0]++; + } else sb.append(ch); + break; + case ' ': + case '\t': + if (sb.length() == 0) break; + default: + sb.append(ch); } } - - @Override - public String toString() { - return name; + if (sb.length() > 0) { + current.add(new Data(sb.toString().trim(), self.getIdentifier()).addPar(self)); + sb.setLength(0); } + if (current == translates && !translates.isEmpty()) { + result[2]++; + for (Data word : words) { + Word w = Word.mkElement(word, translates); + self.putChild(parent, w); + if (carrier != null) carrier.add(w); + } + } else if (!words.isEmpty()) throw report(self.getName() + ":\n" + src.substring(lineStart) + + "\nExpected ';' and text. Reached end of file."); } - /** - * Contains words on one line sorted by their position. - */ - private static class SimpleLine { - - String[][] words; - - public SimpleLine(String[] names) { - words = new String[][]{split(names[0]), split(names[1])}; - } - - private static String[] split(String src) { - if (src.indexOf('\\') == -1) return new String[]{src}; - LinkedList array = new LinkedList<>(); - int i = 0; - for (int j = 0; j < src.length() - 1; j++) { - if (src.charAt(j) == '\\') { - switch (src.charAt(j + 1)) { - case '\\': - case '/': - j++; - break; - default: - array.add(src.substring(i, j)); - i = ++j; - } - } + private String getDescription() { + StringBuilder sb = new StringBuilder(); + char ch; + while (++i < src.length()) { + switch (ch = src.charAt(i)) { + case '\\': + char ch2 = src.charAt(++i); + switch (ch2) { + case ']': + return sb.toString(); + default: + sb.append(ch2); + } + break; + case '\n': + line++; + default: + sb.append(ch); } - array.add(src.substring(i)); - return array.toArray(new String[0]); } + if (i >= src.length()) { + throw report("line " + line + ":\n'" + src.substring(lineStart, i - lineStart > 20 ? lineStart + + 20 : i) + "'\nExpected '\\]' (end of description section). Reached end of file."); + } + return sb.toString().trim(); } + private static IllegalArgumentException report(String info) { + Formatter.defaultReacts.get(SimpleReader.class + ":fail").react(info); + return new IllegalArgumentException(info); + } + public static String[] nameResolver(String name) { List words = new LinkedList<>(); int start = 0; char prev = (char) -1; for (int i = 0; i < name.length(); i++) { char ch = name.charAt(i); - if (prev == '\\' && ch != '\\' && ch != '/' && ch != '/' && ch != ')') { + if (prev == '\\' && ch != '\\' && ch != '/' && ch != '(' && ch != ')') { words.add(name.substring(start, i - 1)); start = i; } diff --git a/Library/src/IOSystem/SimpleWriter.java b/Library/src/IOSystem/SimpleWriter.java index 15c9253..9c24547 100644 --- a/Library/src/IOSystem/SimpleWriter.java +++ b/Library/src/IOSystem/SimpleWriter.java @@ -1,10 +1,10 @@ package IOSystem; -import java.io.File; -import java.io.FileOutputStream; +import IOSystem.Formatter.IOSystem.GeneralPath; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; +import objects.SaveChapter; import objects.Word; import objects.templates.BasicData; import objects.templates.Container; @@ -18,43 +18,88 @@ * * @author Josef Litoš */ -public class SimpleWriter { +public final class SimpleWriter { + + static String wordSplitter = ";"; + static String wordSeparator = "\\"; + /** + * Translates actual wordSplitter to a number representing the correspondent option. + * @return ";", "=", " = ", or " → " + */ + public static String getWordSplitter() { + return wordSplitter; + } + + public static void setWordSplitter(String splitter) { + switch (splitter) { + case " = ": + case " → ": + wordSplitter = splitter; + wordSeparator = " \\ "; + break; + case ";": + case "=": + wordSplitter = splitter; + default: + wordSeparator = "\\"; + } + Formatter.putSetting("exportWordSplit", splitter); + } + + private int tabs = 0; + private final StringBuilder sb = new StringBuilder(); + /** * Saves content of the given container into specified file, adding it to the end of * its content. * * @param dest the file to obtain the content of the source - * @param par parent of the object to be written - * @param src the object to be saved + * @param src pairs of object and parent (in this order) to be saved */ - public static void saveWords(File dest, Container par, Container src) { - try (OutputStreamWriter osw = new OutputStreamWriter( - new FileOutputStream(dest, true), StandardCharsets.UTF_8)) { - osw.append(writeChapter(new StringBuilder(), par, src)); - Formatter.defaultReacts.get(SimpleWriter.class + ":success") - .react(src.getName()); + public SimpleWriter(GeneralPath dest, Container[] ... src) { + try { + for (Container[] couple : src) saveContent(couple[0], couple[1]); + try (OutputStreamWriter osw = + new OutputStreamWriter(dest.createOutputStream(true), StandardCharsets.UTF_8)) { + osw.append(sb); + } + Formatter.defaultReacts.get(SimpleWriter.class + ":success").react("OK"); } catch (Exception e) { Formatter.defaultReacts.get(ContainerFile.class + ":save").react(e, dest, src); } } - public static StringBuilder writeChapter( - StringBuilder sb, Container par, Container src) { - sb.append('\n').append(src.getName()).append("\n{"); - for (BasicData bd : src.getChildren(par)) { + private void saveContent(Container self, Container par) { + indentation().writeData(self, par).append(self instanceof SaveChapter ? " §{\n" : " {\n"); + tabs++; + for (BasicData bd : self.getChildren(par)) { if (bd instanceof Word) { - sb.append('\n').append(bd.getName()).append(';'); + indentation().writeData(bd, self).append(wordSplitter); boolean first = true; - for (BasicData trl : ((Container) bd).getChildren(src)) { + for (BasicData trl : ((Container) bd).getChildren(self)) { if (first) first = false; - else sb.append('\\'); - sb.append(trl.getName()); + else sb.append(wordSeparator); + writeData(trl, self); } + sb.append('\n'); } else if (bd instanceof Container && !(bd instanceof TwoSided)) { - writeChapter(sb, src, (Container) bd); + saveContent((Container) bd, self); } } - return sb.append("\n}"); + tabs--; + indentation().sb.append("}\n"); + } + + private StringBuilder writeData(BasicData bd, Container par) { + sb.append(bd.getName().replaceAll(";", "\\;").replaceAll("=", "\\=").replaceAll("→", "\\→")); + String desc = bd.getDesc(par); + if (desc != null && !desc.isEmpty()) sb.append(" \\[").append(desc).append("\\]"); + return sb; + } + + private SimpleWriter indentation() { + for (int i = tabs; i > 0; i--) sb.append('\t'); + return this; } } diff --git a/Library/src/objects/MainChapter.java b/Library/src/objects/MainChapter.java index 659dac9..fc2e05c 100644 --- a/Library/src/objects/MainChapter.java +++ b/Library/src/objects/MainChapter.java @@ -170,32 +170,32 @@ public boolean move(Container op, Container opp, Container np, Container npp) { @Override public BasicData[] getChildren() { - if (!loaded && children.isEmpty()) load(false); + if (loaded < 2 && children.isEmpty()) load(false); return children.toArray(new BasicData[children.size()]); } @Override public BasicData[] getChildren(Container c) { - if (!loaded && children.isEmpty()) load(false); + if (loaded < 2 && children.isEmpty()) load(false); return getChildren(); } @Override public boolean putChild(Container c, BasicData e) { - if (!loaded && children.isEmpty()) load(false); + if (loaded < 2 && children.isEmpty()) load(false); return children.add(e); } @Override public boolean putChild(Container c, BasicData e, int index) { - if (!loaded && children.isEmpty()) load(false); + if (loaded < 2 && children.isEmpty()) load(false); children.add(index, e); return true; } @Override public boolean replaceChild(Container c, BasicData old, BasicData repl) { - if (!loaded && children.isEmpty()) load(false); + if (loaded < 2 && children.isEmpty()) load(false); int index = children.indexOf(old); if (index > -1) { children.set(index, repl); @@ -211,7 +211,7 @@ public boolean removeChild(Container c, BasicData e) { @Override public Container removeChild(BasicData e) { children.remove(e); - return null; + return this; } /** @@ -255,7 +255,8 @@ public void save(Reactioner rtr, boolean thread) { else r.run(); } - private volatile boolean loaded; + // 0: nothing loaded, 1: loaded main.json file, 2: loaded all saved chapters + private volatile int loaded; private boolean loading; /** @@ -264,15 +265,15 @@ public void save(Reactioner rtr, boolean thread) { */ @Override public void load(Reactioner rtr, boolean thread) { - if (loaded) return; + if (loaded > 1) return; if (thread) new Thread(() -> load0(rtr, true), "MCh load").start(); else load0(rtr, false); } private void load0(Reactioner rtr, boolean thread) { if (children.isEmpty()) { - if (!getSaveFile().exists()) { - loaded = true; + if (!getSaveFile().exists() || loaded > 0) { + loaded = 2; return; } synchronized (this) { @@ -287,7 +288,8 @@ private void load0(Reactioner rtr, boolean thread) { } try { ReadElement.loadFile(getSaveFile(), this, this); - if (children.isEmpty()) loaded = true; + if (SaveChapter.ELEMENTS.get(this) == null) loaded = 2; + else loaded = 1; synchronized (this) { loading = false; notifyAll(); @@ -303,7 +305,7 @@ private void load0(Reactioner rtr, boolean thread) { do for (int i = (size = schs.size()) - 1; i >= 0; i--) if (!schs.get(i).isLoaded()) schs.get(i).load(rtr, thread); while (size < schs.size()); - loaded = true; + loaded = 2; } if (Formatter.defaultReacts.get("MChLoaded") != null) Formatter.defaultReacts.get("MChLoaded").react(this); @@ -311,7 +313,7 @@ private void load0(Reactioner rtr, boolean thread) { @Override public boolean isLoaded() { - return loaded; + return loaded > 1; } @Override diff --git a/Library/src/objects/Picture.java b/Library/src/objects/Picture.java index f43c431..74b9bd3 100644 --- a/Library/src/objects/Picture.java +++ b/Library/src/objects/Picture.java @@ -218,7 +218,11 @@ public static void clean(MainChapter mch) { */ public static boolean isCleanable(MainChapter mch) { Object o = mch.getSetting("imgRemoved"); - return o instanceof Boolean && (boolean) o; + if (o instanceof Boolean && (boolean) o) { + if (ELEMENTS.get(mch) != null && !ELEMENTS.get(mch).isEmpty()) return true; + else mch.removeSetting("imgRemoved"); + } + return false; } /** diff --git a/Library/src/objects/SaveChapter.java b/Library/src/objects/SaveChapter.java index a5a7ea4..2845dae 100644 --- a/Library/src/objects/SaveChapter.java +++ b/Library/src/objects/SaveChapter.java @@ -162,7 +162,7 @@ public boolean hasChild(Container par, BasicData e) { public Container removeChild(BasicData e) { if (!loaded) load(false); Container ret = super.removeChild(e); - if (children.isEmpty()) getSaveFile().delete(); + if (ret != null && children.isEmpty()) getSaveFile().delete(); return ret; } @@ -170,7 +170,7 @@ public Container removeChild(BasicData e) { public boolean removeChild(Container c, BasicData e) { if (!loaded) load(false); boolean ret = super.removeChild(c, e); - if (children.isEmpty()) getSaveFile().delete(); + if (ret && children.isEmpty()) getSaveFile().delete(); return ret; } @@ -269,7 +269,11 @@ public static void clean(MainChapter mch) { */ public static boolean isCleanable(MainChapter mch) { Object o = mch.getSetting("schRemoved"); - return o instanceof Boolean && (boolean) o; + if (o instanceof Boolean && (boolean) o) { + if (ELEMENTS.get(mch) != null && !ELEMENTS.get(mch).isEmpty()) return true; + else mch.removeSetting("schRemoved"); + } + return false; } @Override diff --git a/Library/src/objects/templates/ContainerFile.java b/Library/src/objects/templates/ContainerFile.java index e43d9e0..264cf5b 100644 --- a/Library/src/objects/templates/ContainerFile.java +++ b/Library/src/objects/templates/ContainerFile.java @@ -1,7 +1,5 @@ package objects.templates; -import java.io.File; - import IOSystem.Formatter.Reactioner; import static IOSystem.Formatter.defaultReacts; @@ -56,8 +54,7 @@ default void save(boolean thread) { void save(Reactioner rtr, boolean thread); /** - * @return if this object has already loaded all its content from its - * {@link #getSaveFile() file}. + * @return if this object has already loaded all its content from its {@link #getSaveFile() file}. */ boolean isLoaded(); diff --git a/SchoolManager.apk b/SchoolManager.apk index 87e9593..fd7f512 100644 Binary files a/SchoolManager.apk and b/SchoolManager.apk differ