<jupyter_start><jupyter_text>**JSON2RTF**  ÚvodCílem aplikace je vytvořit z přijatých vstupních souborů (data v JSON a formulář v rtf) vyplněný výstupní soubor rtf. Příkladem praktického využití je situace, kdy máme v JSON uloženou databázi zaměstanců a chceme z ní vytisknout určitá data - např. seznam zaměstanců, kde u každého bude uvedeno jeho bydliště. Vytvoříme si tedy pomocí MS Office Word (nebo podobného editoru) formulář, který opatříme flagy, podle kterých bude formulář vyplněn.Aplikace se skládá z knihovny JSON2RTF_lib a serveru JSON2RTF_server který implementuje JSON2RTF_lib. Server zajišťuje příjem vstupních souborů a odeslání výstupního souboru. Zároveň implementuje funkce pro zabezpečení pomocí tokenu. Knihovna pak slouží pro samotné zpracování přijatých souborů do výsledného vyplněného formuláře.Pro demonstaraci funkčnosti obsahuje aplikace testovací stránku na platformě Swagger, které umožňuje nahrát vybrané soubory a zobrazit výstup.Pro správnou funkčnost aplikce je nutné, aby uživatel byl poučený a veděl, jak správně napsat vstupní formulář - aplikace neobsahuje funkce pro kontrolu kompatibility rtf a JSON.  Nasazení aplikaceAplikace je z důvodu zajištění kompatibility provozována v prostředí Docker. Obsahuje jak soubor DOCKERFILE pro vytvoření image, tak i docker-compose.yaml který slouží ke spuštení kontejneru.Pro spuštění aplikace je nutné v adresáři s aplikací použít příkaz docker-compose upUI aplikace je pak defaultně na localhost:80\docsAplikace je publikována pod MIT licencí na __[GitHub](https://github.com/MrM2266/JSON2RTF_Lib)__ a __[hub.docker.com](https://hub.docker.com/repository/docker/marek2266/json2rtf_server)__  DockerPro zajištění kompatibilty je nutné mít naistalovaný Docker a Docker Compose (na Windows oba součástí Docker Hub). Adresu a port, na kterém bude aplikace dostupná v rámci kontejneru, je možné nastavit v DOCKERFILE - obsahuje parametr pro uvicorn a FastAPI.Adresu a port, na kterém bude aplikace dostupná z hostitelského pc (nebo v rámci sítě) je možné nastavit v docker-compose.yamlPro stažení z __[hub.docker.com](https://hub.docker.com/repository/docker/marek2266/json2rtf_server)__ je možné použít příkaz: docker pull marek2266/json2rtf_server:latest  UISoučástí aplikace je jednoduché UI, které je ve výchozím nastavení dostupné na localhost:80/docs. Umožňuje nahrát vstupní soubory a zobrazit výstup. Jedná se o testovací prostředí vytvořené pomocí FastAPI a Swagger. Je zde také možné otestovat zabezpečení pomocí tokenu. Defaultní údaje:Username: jirkaLogin: secret  JSON filePodle IESG Standarts Track 2070-1721 RFC8259 může JSON soubor obsahovat pouze value. Value může být: number, string, boolean, array, object a null. Každý json soubor může obsahovat jen jednu value. Jednotlivé value je možné do sebe libovolně vnořovat.**Pro správné zobrazení diakritiky je nutné json uložit s formátováním Windows-1250 (cp1250)****array**- je to seřazený soubor hodnot, značí se [ ]- k hodnotám je možné přistoupit pomocí jejich indexu (key = index)- v poli mohou být pouze hodnoty oddělené čárkou - tzn. number, string, boolean, array, object a null - [10, "text",true, [ ], { }, null] - prvky pole mají jen index**object**- je to neuspořádaný soubor hodnot, značí se { }- k hodnotám je možné přistoupit pomocí jejich key- prvky jsou ve tvaru "key":"value" kde value je number, string, boolean, array, object nebo null- prvky se oddělují čárkou{	"number":8,	"string":"text",	"boolean":true,	"object":{},	"array":[],	"null":null}**number**- je to jakékoliv číslo- př. 26**string**- je to jakýkoliv text- př. "text"**boolean**- true nebo false**null**- pouze null  Flagy v RTFPro označení míst, která mají být ve výtupním rtf nahrazena daty z JSON je nutné použít flagy. Flagy se píší do vstupního formuláře. Cílem flagu je označit místo, které má být nahrazeno daty z JSON a popsat, kde se požadovaná data v JSON nacházejí. JSON soubor má strukturu úrovní - je možné vnořovat libovolně prvky do sebe. Flagy definují úroveň (level), ve které se hledaná data nacházejí.Sytax flagů se řídí následujícími pravidly, která částečně vycházejí z pravidel pro JSON:1. Nejvyšší úroveň json i rtf se nazývá root1. Flagy se vždy píší do hranatých závorek ve tvaru [[flag:key]]1. v rootu musí být pouze jedno pole nebo jeden objekt1. součástí flagu je key, který obsahuje název konkrétní struktury v JSON např. [[A:pole]] - zde key = pole1. prvky pole mají index - jako key se používá slovo null - př. [[O:null]][[E:null]] označuje objekt v poli1. key musí být unikátní v rámci levelu1. jako key v json nelze použít null1. každá struktura kromě item musí mít end flag Rozlišujeme následující flagy:**end**[[E:key]]- označuje konec struktury**object**[[O:key]] - označuje začátek objektu key - program vyhledá v aktuální úrovni json souboru objekt auto a vstoupí do něj[[E:key]] - označuje konec objektu key - program ukončí hledání v objektu auto a přesune se v json souboru o úroveň nahoru- odpovídá hodnotě object v json**array**[[A:key]] - označuje začátek pole key - program přejde z aktuálního levelu v json souboru do pole key[[E:key]] - označuje konec pole key - program se přesune v json souboru o level nahoru- Flag array je specifický tím, že kód, který je mezi start flagem [[A:lide]] a end flagem [[E:lide]] se provede pro každý prvek pole.- Program spočítá počet prvků v json (např. pole lide obsahuje v json 5 objektů) a provede zadaný kód 5x -> kód mezi [[A:key]] a [[E:key]] se provede 5x po sobě, pokaždé pro jeden prvek pole key- odpovídá hodnotě array v json**item**[[I:key]]- označuje místo, které se má nahradit z json konkrétními daty - program v json objektu najde položku s názvem key a místo [[I:key]] doplní data z json- odpovídá hodnotám number, string, boolean, null v jsonMožné kombinace a jejich zápis pomocí flagů:**Root může obsahovat:**[[A:root]] - nepojmenované pole v rootu; end flag [[E:root]][[O:root]] - nepojmenovaný objekt v rootu;end flag [[E:root]]**Array může obsahovat:**[A:null]] - nepojmenované pole v poli; end flag [[E:null]][[O:null]] - nepojmenovaný objekt v poli; end flag [[E:null]][[I:index]] - index čísla, stringu nebo booleanu pro vypsání**Object může obsahovat:**[[A:key]] - pro pole v objektu - musí být pojmenované (musí mít strukturu key:value); end flag [[E:key]][[O:key]] - pro objekt v objektu - musí bý pojmenovaný; end flag [[E:key]][[I:key]] - pro string, číslo, boolean v objektu - musí být pojmenovaný**Vypsání všech prvků pole:**- v json máme pole, které obsahuje pouze stringy, nebo čísla - chceme celé pole vypsat- pomocí flagů [[A:key]][[E:key]] spustíme na poli key fci ArrayAsString, která vypíše prvky pole jako stringy oddělené čárkami  Postup při vytváření šablony pro vyplnění1. V MS Word vytvoříme šablonu - s formátováním, tabulkami atd. - uložíme jako rtf1. Šablonu otevřeme v notepadu - dopíšeme flagy a uložíme jako rtf1. odešleme spolu s příslušným JSON na server  Popis funkcionality knihovny JSON2RTF_libTato kapitola se bude věnovat hlubšímu popisu knihovny. Bude ilustrovat jednotlivé funkce a popíše způsob, jakým jsou soubory zpracovány.  ObecněKnihovna obsahuje dvě základní třídy CData a CjsonReader. Třída CData má dvě instance - input a output. Slouží pro uložení vstupního a výstupního rtf jako string. Třída má proměnnou m_data, která obsahuje rtf. Poskytuje základní metody - LoadData a Add. Díky nim umí input i output načíst data a přidat si do m_data část vygenerovaného rtf.Třída CjsonReader zajišťuje "pohyb" v json souboru a čtení dat z něj. Poskytuje metody Down, Up a NextElement  pro "pohyb" v levelech JSON a metody GetArraySize a GetRootSize pro zjištění velikosti polí. Metody GetArrayAsString a GetRootAsString vracejí prvky pole jako string. Metoda GetItem pak vrací data z JSON objektu.Obecně knihovna funguje tak, že celý rtf soubor se předá do fce Process. Ta najde první start flag v řetezci input. Vše co je před start flagem přidá do output. K start flagu najde odpovídající end flag. Vše co je před end flagem odstarní z input (data jsou zpracována). Podle flagu (A, O, I) spustí odpovídající funkci - Array, Object, Item a předá jí vše, co je mezi start flagem a end flagem. Funkce spuštná podle flagu (Array, Object, Item) opět předává svou část do fce Process -> je možné jít do libovolné úrovně JSON - JSON se neustále "rozbaluje".Funkce se takto volají neustále mezi sebou, až string m_data v input neobsahuje žádné znaky - výsledný rtf je zpracovaný.  Hledání FlagůChceme zpracovat rtf soubor uložený ve stringu inputRTF<jupyter_code>inputRTF = "rtf kod se speciálními znaky ,.ů§/*86 [[O:auto]] kod objektu auto[[E:auto]] pokračování rtf kódu"<jupyter_output><empty_output><jupyter_text>Nejprve si definujeme třídu CData, jejíž instance budou input a output - bude se starat o vstupní a výstupní rtf.<jupyter_code>class CData:
    def __init__(self):
        self.m_data=""

    def Add(self, add):
        """Gets a string that is added to m_data
	
	    Args:
		add: value that is added to m_data
	    """
        self.m_data += str(add)

    def LoadData(self, data):
        """Takes rtf file as a string and stores it into m_data
	
	    Args:
		    data: rtf as string - from fastAPI
	    """
        self.m_data = data<jupyter_output><empty_output><jupyter_text>Vyhledáme první start flag - fce FlagStart vrací array ve tvaru [flag, key, počátek start flagu, konec start flagu]. String inpurRTF získáme např. z FastAPI<jupyter_code>import re

def FlagStart(data):
     """Finds the first flag in a string of data and finds out information  
     
     Information: What flag is it, it's key, beginning and end
	  
     Args:
         data: String of data you want to find the information about
     
     Returns:
         [flag, key, match.start(), match.end()]: if the functions finds out the information
         None: if the function doesn't find out any information  
     """
     match = re.search("\[{2}((A:[a-zA-Z0-9]+)|(O:[a-zA-Z0-9]+)|(I:[a-zA-Z0-9]+))\]{2}", data)
     if match:
         flag = data[match.start() + 2 : match.start() + 3]
         key = data[match.start() + 4 : match.end() - 2]
         return [flag, key, match.start(), match.end()]
     else:
         return None

input = CData()
output = CData()
input.LoadData(inputRTF)

outputStart = FlagStart(input.m_data)

print(f"Flag: {outputStart[0]}")
print(f"Key: {outputStart[1]}")
print(f"Pozice prvního znaku start flagu: {outputStart[2]}")
print(f"Pozice posledního znaku start flagu: {outputStart[3]}")<jupyter_output>Flag: O
Key: auto
Pozice prvního znaku start flagu: 38
Pozice posledního znaku start flagu: 48<jupyter_text>Funkce vyhledala v řetězci první start flag a jako výstup předává všechny potřebné informace - key, pozice prvního a posledního znaku.Pomocí funkce FlagEnd vyhledáme na zadaném řetězci end flag s konkrétním key<jupyter_code>def FlagEnd(data, key):
    """Returns the positon of the ending tag
	
    Args:
        data: String of data in which you want to find the position of the ending tag
        key: Value of the ending tag

    Returns:
        Positon of the ending tag
        Beginning and ending of the tag
    """
    if (key == "null"):
        list = []
        start=[]
        end=[]

        for match in re.finditer("\[{2}(A|O):null\]{2}", data):
            s = match.start()
            list.append(s)
            start.append(s)
        for match in re.finditer("\[{2}E:null\]{2}", data):
            s = match.start()
            list.append(s)
            end.append(s)

        list.sort()
        count=0

        for i in list:
            if i in start:
                count += 1
            if i in end:
                count -= 1
            if (count == 0):
                return [i,i+10]
    else:
        str = "[[E:" + key + "]]"
        return [data.find(str), data.find(str) + len(str)]
    

outputEnd = FlagEnd(inputRTF, outputStart[1])

print(f"Pozice prvního znaku end flagu: {outputEnd[0]}")
print(f"Pozice posledního znaku end flagu: {outputEnd[1]}")<jupyter_output>Pozice prvního znaku end flagu: 65
Pozice posledního znaku end flagu: 75<jupyter_text>Výstup z obou funkcí používá funkce Process, která předá část vstupního rtf do výstupu, odstraní flagy a kód mezi nimi předá do příslušné funkce. Funkce jsou uvedené v modifikované podobě - pouze zobrazují data, která přijímají jako parametry.<jupyter_code>def Process(data):
    """Takes string data and finds the first tag from the beginning (start tag) and corresponding end tag
    Text that is before start flag is added to output
    Text that is after end flag is left for another loop
    The code between the start flag and the end flag is passed into Process function again

    Args:
        data: string to process

    Returns:
        str: text that is after the end flag - code for another loop
    """

    start = FlagStart(data)
    if start != None:
        output.Add(data[0:start[2]])
    
        if start[0] != "I":
            end = FlagEnd(data, start[1])
            if start[0] == "A":
                Array(data[start[3]:end[0]], start[1])
            if start[0] == "O":
                Object(data[start[3]:end[0]], start[1])
            return data[end[1]:]
        else:
            Item(start[1])
            return data[start[3]:]
    else:
        output.Add(data)
        return ""
    
def Array(data, key):
    print("Funkce Array\n=============================================")
    print(f"Key k vyhledání v json: {key}")
    print(f"Kód pole: {data}")

    

def Object(data, key):
    print("Funkce Object\n=============================================")
    print(f"Key k vyhledání v json: {key}")
    print(f"Kód objektu: {data}")


def Item(key):
    print("Funkce Key\n=============================================")
    print(f"Key k vyhledání v json: {key}")


input.m_data = Process(input.m_data)<jupyter_output>Funkce Object
=============================================
Key k vyhledání v json: auto
Kód objektu:  kod objektu auto<jupyter_text>V tuto chvíli jsme schopni dekódovat první úroveň flagů. Funkce Array a Object ve skutečnosti nezobrazují výstup, ale znovu svůj kód předají do funkce Process - v kódu např. objektu dojde k vyhledání dalších struktur a spuštění příslušných funkcí - v objektu je např. pole -> funkce Process spustí funkci Array - ta opět provede analýzu svého kódu pomocí Process -> kód může jít do libovolné úrovně.V tomto příkladu popíšeme pouze práci s první úrovní - inputRTF obsahuje pouze jednu dvojici start flag - end flag  Vyhledávání v JSONBudeme ilustrovat funkci třídy CjsonReader na stringu inputJson, který obsahuje vstupní JSON soubor (např. z FastAPI)<jupyter_code>inputJson = """
{
  "states": [
    {
      "name": "Alabama",
      "abbreviation": "AL",
      "areaCodes": [ "205", "251", "256", "334", "938" ]
    },
    {
      "name": "Alaska",
      "abbreviation": "AK",
      "areaCodes": [ "907" ]
    },
    {
      "name": "Arizona",
      "abbreviation": "AZ",
      "areaCodes": [ "480", "520", "602", "623", "928" ]
    },
    {
      "name": "Arkansas",
      "abbreviation": "AR",
      "areaCodes": [ "479", "501", "870" ]
    }]
}
"""<jupyter_output><empty_output><jupyter_text>Definujeme si třídu CjsonReader, který bude zajišťovat práci s JSON souborem a vyhledávání v něm.<jupyter_code>import json

class CjsonReader:
    def __init__(self):
        self.m_levels = [] #list of individual levels, current is always the last item of the array
        self.m_level = 0
        self.m_indexes = [0] #list of indexes (index 0 contains value 2 -> I'am on an index 2 on level 0

    def LoadData(self, jsonString, decode):
        """Takes JSON as bytes. If decode is 1 JSON is decoded using ANSI and then stored into m_levels.

        Args:
            jsonString: jsonFile as bytes - from fastAPI
            decode: parameter; if 1 -> string is decoded using cp1250; if 0 -> string is stored without decoding
        """
        if (decode == 1):
            self.m_levels.append(json.loads(str(jsonString, 'cp1250')))
        else:
            self.m_levels.append(json.loads(jsonString))

    def Down(self, key):
        """Gets you down a level to entered key or index
        
	    Args:
		    key: Index or key you want to step down to 
	    """
        self.m_levels.append(self.m_levels[self.m_level][key])
        self.m_indexes.append(0)
        self.m_level += 1

    def Up(self):
        """Gets you up a level
	    """
        if self.m_level > 0:
            del self.m_levels[-1]
            del self.m_indexes[-1]
            self.m_level -= 1

    def GetItem(self, key):
        """Returns string from JSON dictionary
	
	    Args:
	        key: JSON dictionary key to find desired string

	    Returns:
		    str: string found under parameter key
        """
        return self.m_levels[self.m_level][key]

    def GetArraySize(self, key):
        """Returns the size of an array
	
	    Args:
            key: Key of an array you want to get the size of

	    Returns:
		    int: Size of an array
        """
        return len(self.m_levels[self.m_level][key])

    def GetRootSize(self):
        """Returns the size of root array

	    Returns:
		    int: Size of root
        """
        return len(self.m_levels[0])

    def GetArrayAsStr(self, key):
        """Returns array as one string - elements are separated with ,
	
	    Args:
            key: Key of an array you want to get as a string

	    Returns:
		    int: Size of anarray
        """
        result = ""
        for i in self.m_levels[self.m_level][key]:
            result = result + str(i) + ", "
        return result[:-2]

    def GetRootAsStr(self):
        """Returns root array as one string - elements are separated with ,
	
	    Args:
            key: Key of an array you want to get as a string

	    Returns:
		    int: Size of root
        """
        result = ""
        for i in self.m_levels[0]:
            result = result + str(i) + ", "
        return result[:-2]

    def NextElement(self):
        """Moves you one item forward in current level (only in array)
        """
        self.m_indexes[self.m_level] += 1

    def GetIndex(self):
        """Returns index of processing element on current level

        Returns:
            int: index of an element
        """
        return self.m_indexes[self.m_level]<jupyter_output><empty_output><jupyter_text>V následujícím kódu si vytvoříme instanci třídy CjsonReader a pomocí jejích metod budeme zobrazovat data z inputJson.<jupyter_code>jsonData = CjsonReader()
jsonData.LoadData(inputJson, 0)

jsonData.Down("states") ##v souboru je objekt, který obsahuje pole states; z root objektu vstoupíme do states
jsonData.Down(0) ##v poli states vstoupíme do prvku 0

print("Data z prvku 0\n==================================")
print(jsonData.GetItem("name")) ##vypíšeme položku name státu s indexem 0
print(jsonData.GetItem("abbreviation"))
print(jsonData.GetArrayAsStr("areaCodes")) ##vrací pole jako string

jsonData.Up() ##vrátíme se o úroveň nahoru - z prvku 0 do pole states
jsonData.Down(1) ##vstoupíme do prvku 1

print("\n\nData z prvku 1\n==================================")
print(jsonData.GetItem("name")) ##vypíšeme položku name státu s indexem 0
print(jsonData.GetItem("abbreviation"))
print(jsonData.GetArrayAsStr("areaCodes")) ##vrací pole jako string

jsonData.Up() ##vrátíme se o úroveň nahoru - z prvku 1 do pole states
jsonData.Down(2) ##vstoupíme do prvku 2

print("\n\nData z prvku 2\n==================================")
print(jsonData.GetItem("name")) ##vypíšeme položku name státu s indexem 0
print(jsonData.GetItem("abbreviation"))
print(jsonData.GetArrayAsStr("areaCodes")) ##vrací pole jako string

jsonData.Up() ##jsme v poli states


print("\n\nSeznam států v json\n==================================")
jsonData.Up()
pocet = jsonData.GetArraySize("states") ##vrací velikost pole states
jsonData.Down("states")

for i in range(0,pocet):
    jsonData.Down(i)
    print(jsonData.GetItem("name"))
    jsonData.Up()<jupyter_output>Data z prvku 0
==================================
Alabama
AL
205, 251, 256, 334, 938


Data z prvku 1
==================================
Alaska
AK
907


Data z prvku 2
==================================
Arizona
AZ
480, 520, 602, 623, 928


Seznam států v json
==================================
Alabama
Alaska
Arizona
Arkansas<jupyter_text>Generování výstupuKnihovna JSON2RTF_lib propojuje obě třídy dohromady a podle toho, jak čte rtf flagy, tak provádí čtení z JSON souboru. V následujícím kódu bude ukázána funkce celé knihovny. Na vstupu budeme mít dva stringy - jeden obsahuje data z vdtupního rtf formuláře a druhý obsahuje data z JSON.V kódu bude použita hotová knihovna JSON2RTF_lib.py tak, jak jí používá JSON2RTF_server.<jupyter_code>%reset -f

inputJson = """
{
  "states": [
    {
      "name": "Alabama",
      "abbreviation": "AL",
      "areaCodes": [ "205", "251", "256", "334", "938" ]
    },
    {
      "name": "Alaska",
      "abbreviation": "AK",
      "areaCodes": [ "907" ]
    },
    {
      "name": "Arizona",
      "abbreviation": "AZ",
      "areaCodes": [ "480", "520", "602", "623", "928" ]
    },
    {
      "name": "Arkansas",
      "abbreviation": "AR",
      "areaCodes": [ "479", "501", "870" ]
    }]
}
"""
inputRTF = "[[O:root]]Seznam statu \n================\n[[A:states]][[O:null]]Stat: [[I:name]]\n[[E:null]][[E:states]][[E:root]]"
#inputRTF = "[[O:root]]Seznam statu \n================\n\n[[A:states]][[O:null]]Stat: [[I:name]]\nKod: [[I:abbreviation]]\nOblasti: [[A:areaCodes]][[E:areaCodes]]\n\n[[E:null]][[E:states]][[E:root]]"
import JSON2RTF_lib as RTF

RTF.Init()
RTF.LoadJson(inputJson, 0)
RTF.LoadRTF(inputRTF)

RTF.ProcessRTF()

outputRTF = RTF.GetOutput()

print(outputRTF)<jupyter_output>Seznam statu 
================
Stat: Alabama
Stat: Alaska
Stat: Arizona
Stat: Arkansas
{'fraction_non_alphanumeric': 0.11884270280168467, 'fraction_numerical': 0.013367515107123237, 'mean_word_length': 3.569127797531897, 'pattern_counts': {'":': 36, '<': 32, '<?xml version=': 0, '>': 40, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '63974', 'license': 'MIT', 'n_tokens_mistral': 8190, 'n_tokens_neox': 8144, 'n_words': 2540, 'path': 'JSON2RTF.ipynb', 'repo_name': 'MrM2266/JSON2RTF_Lib', 'size': '21844'}