
com.sta.cts.XMLScanner Maven / Gradle / Ivy
package com.sta.cts;
import java.io.InputStream;
import java.io.IOException;
import java.io.BufferedInputStream;
import java.util.Date;
import java.util.Hashtable;
import com.sta.mlogger.MLogger;
/**
* Name: XMLScanner
* Description:
* Stellt die Funktionalit?t "Lesen einer XML-Datei" als Scanner im
* Compilerbau-technischen Sinne zur Verf?gung. Durch Ableitung von Scanner
* wird ein One-Token-Lookahead realisiert. Eine Zeichen-Decodierung wird
* automatisch durchgef?hrt, falls die Init-Methoden mit Dateinamen oder
* InputStream verwendet werden. Unterst?tzt werden 8-Bit-Encodings,
* insbesondere ISO-8859-1 und UTF-8. Derzeit nicht unterst?tzt werden
* 16-Bit-Encodings wie UTF-16.
*
* Copyright: Copyright (c) 2001-2004, 2011-2014, 2016-2019, 2021
* Company: >StA-Soft<
* @author StA
* @version 1.0
*/
public class XMLScanner extends Scanner
{
/**
* Operations-Level f?r XML-Bl?tter.
*/
protected int myLevel = 0;
/**
* Aktuell zu verwendende XML-Blatt-Hash-Tabelle.
*/
protected LeafHashtable myLHT = null;
/**
* Entity-Aufl?sung, falls gew?nscht.
*/
private Hashtable myEntities = null;
//===========================================================================
/**
* Standard-Constructor.
*/
public XMLScanner()
{
}
//===========================================================================
/**
* Aufzul?sende Entities vorgeben.
* @param pEntities aufzul?sende Entities
*/
public void setEntities(Hashtable pEntities)
{
myEntities = pEntities;
}
/**
* Zeichen vor dem erstern "<" ?berlesen, insbesondere f?r UTF-8 gedacht.
* (Nur falls n?tig?)
*/
protected void preOverread()
{
try
{
char ch = getChar();
while ((ch != 0x00) && (ch != '<'))
{
ch = getChar();
}
ungetChar(ch);
}
catch (IOException e)
{
}
}
/**
* XML-Header (PI "xml") ?berlesen.
* Das erfolgt in init(is, enc) ab sofort (30.04.2014) immer automatisch,
* also praktisch immer dann, wenn der XMLScanner mit einem Stream
* initialisiert wird, was wiederum auch dann erfolgt, wenn der XMLScanner
* mit einem Dateinamen initialisiert wird.
*
* Diese Methode hier muss nur dann aufgerufen werden, wenn der XMLScanner
* mit einem Reader initialisiert wird, weil dann das ?berlesen nicht
* automatisch erfolgt. Der Aufruf st?rt jedoch im Stream-Fall nicht.
*
* @return Encoding, falls vorhanden, sonst null
*/
public String initXS()
{
String encoding = null;
// Kopfzeile, falls vorhanden, ?berlesen
Object obj = getToken();
if (obj instanceof XMLTag)
{
XMLTag t = (XMLTag) obj;
if (t.isPI() && t.isOpen() && t.isClose() && t.getName().equals("xml"))
{
// overread
encoding = t.getAttr("encoding");
}
else
{
ungetToken(obj);
}
}
// Anmerkungen: Strings sollten hier nicht auftreten und falls doch, dann
// stimmt die XML-Struktur nicht. Und null-Werte zur?ckzulegen macht keinen
// Sinn. Daher gen?gt obige einfachere Variante.
/*
boolean unget = true;
Object obj = getToken();
if (obj instanceof XMLTag)
{
XMLTag t = (XMLTag) obj;
if (t.isPI() && t.isOpen() && t.isClose() && t.getName().equals("xml"))
{
unget = false;
}
}
if (unget)
{
ungetToken(obj);
}
*/
return encoding;
}
@Override
public void init(InputStream is, String enc) throws IOException
{
if (!is.markSupported())
{
is = new BufferedInputStream(is);
}
if ((enc == null) && is.markSupported())
{
boolean overread = false;
// is.mark(8192);
is.mark(16384);
super.init(is, enc); // !!!Mit "enc", damit kein Zyklus entsteht!!!
preOverread();
Object obj = getToken();
if (obj instanceof XMLTag)
{
XMLTag t = (XMLTag) obj;
if (t.isPI() && t.isOpen() && t.isClose() && t.getName().equals("xml"))
{
enc = t.getAttr("encoding");
overread = true;
}
}
is.reset();
super.init(is, enc);
preOverread();
if (overread)
{
getToken();
}
}
else
{
super.init(is, enc);
boolean unget = true;
Object obj = getToken();
if (obj instanceof XMLTag)
{
XMLTag t = (XMLTag) obj;
if (t.isPI() && t.isOpen() && t.isClose() && t.getName().equals("xml"))
{
unget = false;
}
}
if (unget)
{
ungetToken(obj);
}
}
}
@Override
public void initH(Hashtable pHT)
{
// preOverread(); // siehe: init(is, enc)
}
//---------------------------------------------------------------------------
/**
* ?berlie?t Spaces incl. #$09, #$0d, #$0a. Bei anderen Zeichen erfolgt ein
* Abbruch. Das entscheidende Zeichen wird gelesen und zur?ckgeliefert.
* @return letztes und entscheidendes Zeichen
* @throws IOException falls ein Lesefehler auftritt
*/
protected char overreadSpaces() throws IOException
{
char ch;
do
{
ch = getChar();
}
while ((ch == ' ') || (ch == 0x09) || (ch == 0x0d) || (ch == 0x0a));
return ch;
}
/**
* Namen scannen und zur?ckliefern. Erlaubt sind Ziffern, Buchstaben und
* folgende Sonderzeichen: _ - : .
* Konnte kein Name gelesen werden, wird ein Leer-String geliefert.
* @return Name als String
* @throws IOException falls ein Lesefehler auftritt
*/
protected String scanNameNS() throws IOException
{
char ch;
StringBuilder sb = new StringBuilder();
ch = overreadSpaces();
while (((ch >= '0') && (ch <= '9')) ||
((ch >= 'A') && (ch <= 'Z')) ||
((ch >= 'a') && (ch <= 'z')) ||
((ch == '_') || (ch == '-') || (ch == ':') || (ch == '.'))
)
{
sb.append(ch);
ch = getChar();
}
ungetChar(ch);
return sb.toString();
}
/**
* ?berlesen aller Zeichen bis Ch1 oder Dateiende erreicht. Das entscheidende
* Zeichen wird zur?ckgelegt.
* @param pCh1 das Zeichen, bis zu dem alle anderen Zeichen ?berlesen werden
* sollen.
* @throws IOException falls ein Lesefehler auftritt
*/
protected void overread(char pCh1) throws IOException
{
char ch;
ch = getChar();
while ((ch != pCh1) && (ch != 0x00))
{
ch = getChar();
}
ungetChar(ch);
}
/**
* Routine speziell zum ?berlesen von XML-Kommentaren.
* @param pCh1 das Zeichen, bis zu dessen doppeltem Auftreten alle anderen
* Zeichen ?berlesen werden sollen.
* @throws IOException falls ein Lesefehler auftritt
*/
protected void overread2(char pCh1) throws IOException
{
char ch;
do
{
ch = getChar();
while ((ch != pCh1) && (ch != 0x00))
{
ch = getChar();
}
ch = getChar();
}
while (ch != pCh1);
// ungetChar(ch);
}
/**
* Konvertiert Entities im Parameter-String in die entsprechenden Zeichen.
* @param s Parameter-String mit Entities
* @return String mit aufgel?sten Entities
* @throws IOException im Fehlerfall
*/
protected String convert(String s) throws IOException
{
if (s == null)
{
return null;
}
StringBuilder sb = new StringBuilder();
int len = s.length();
for (int i = 0; i < len; i++)
{
char ch = s.charAt(i);
if (ch == '&')
{
int j = s.indexOf(';', i);
if (j < 0)
{
throw new IOException("Missing ';' for entity (" + s.substring(i) + ").");
}
if (j == i)
{
throw new IOException("Missing entity name for (" + s.substring(i) + ").");
}
String s1 = s.substring(i + 1, j);
i = j;
if (s1.equals("amp"))
{
sb.append('&');
}
else if (s1.equals("lt"))
{
sb.append('<');
}
else if (s1.equals("gt"))
{
sb.append('>');
}
else if (s1.equals("quot"))
{
sb.append('"');
}
else if (s1.equals("apos"))
{
sb.append('\'');
}
else if ((s1.length() > 0) && (s1.charAt(0) == '#'))
{
int k;
if ((s1.length() > 1) && (s1.charAt(1) == 'x'))
{
try
{
k = Integer.parseInt(s1.substring(2), 16);
}
catch (NumberFormatException e)
{
throw new IOException("Hex value expected.");
}
}
else
{
try
{
k = Integer.parseInt(s1.substring(1));
}
catch (NumberFormatException e)
{
throw new IOException("Hex value expected.");
}
}
sb.append((char) k);
}
else
{
Integer code = myEntities != null ? myEntities.get(s1) : null;
if (code != null)
{
sb.append((char) code.intValue());
}
else
{
String sx = "Invalid entity name (&" + s1 + ";).";
MLogger.err(sx);
throw new IOException(sx);
}
}
}
else
{
sb.append(ch);
}
}
return sb.toString();
}
/**
* Inhalt scannen. Es werden (vorerst) alle Zeichen bis '<' bzw. Dateiende
* (excl.) zu einem String zusammengefa?t. Dieser wird zur?ckgeliefert.
* Das entscheidende Zeichen wird zur?ckgelegt.
* Zuk?nftig besteht die M?glichkeit, eine Liste von Strings zu liefern, in
* der jede Text-Zeile als ein separater String enthalten ist (Vector of
* Strings).
* @return normalerweise/bisher ein String, der den Inhalt enth?lt.
* @throws IOException falls ein Lesefehler auftritt
*/
protected Object scanContent() throws IOException
{
char ch;
StringBuilder sb = new StringBuilder();
ch = getChar();
while ((ch != 0x00) && (ch != '<'))
{
sb.append(ch);
ch = getChar();
}
ungetChar(ch);
return convert(sb.toString().trim()); // new: convert (11.08.2003)
}
/**
* Ein Attribut scannen und im Tag eintragen. F?hrende Spaces werden nicht
* ?berlesen.
* @param pTag das aktuelle XML-Tag
* @return true, falls alles Ok. (Name, Eq, Wert in Hochkommata)
* @throws IOException falls ein Lesefehler auftritt
*/
protected boolean scanAttr(XMLTag pTag) throws IOException
{
char ch;
boolean b = false;
StringBuilder sb = new StringBuilder();
String n = scanNameNS();
if (n.length() != 0)
{
ch = overreadSpaces();
if (ch == '=')
{
ch = overreadSpaces();
if ((ch == '"') || (ch == '\''))
{
char ch1 = getChar();
while ((ch1 != ch) && (ch1 != 0x00))
{
sb.append(ch1);
ch1 = getChar();
}
}
else
{
while ((ch != ' ') && (ch != 0x00) && (ch != 0x0d) && (ch != 0x0a) && (ch != '>'))
{
sb.append(ch);
ch = getChar();
}
ungetChar(ch);
}
b = true;
pTag.setAttr(n, convert(sb.toString())); // new: convert (11.08.2003)
}
}
return b;
}
/**
* Scannen eines XML-Tags. Das '<' sollte bereits verarbeitet sein,
* weitere f?hrende Spaces werden ?berlesen.
* @return das XML-Tag oder null (Fehler)
* @throws IOException - falls ein Fehler beim Lesen auftritt oder ein
* logischer Fehler im Aufbau der XML-Datei vorliegt.
*/
protected XMLTag scanTag() throws IOException
{
char ch;
XMLTag tag = null;
ch = overreadSpaces();
if (ch == '!')
{
ch = getChar();
if (ch == '-')
{
ch = getChar();
if (ch == '-')
{
overread2('-');
ch = getChar();
if (ch != '>')
{
throw new IOException("Missing '>'.");
}
}
else
{
ungetChar(ch);
overread('>');
ch = getChar(); // '>' selbst
}
}
else
{
ungetChar(ch);
overread('>');
ch = getChar();
}
}
else if (ch == '?')
{
tag = new XMLTag();
tag.setPI();
tag.setOpen();
// ungetChar(ch);
// tag.setName(scanName());
tag.setNameNS(scanNameNS());
ch = overreadSpaces();
boolean ok = true;
while ((ch != 0x00) && (ch != '?') && (ch != '>') && ok)
{
ungetChar(ch);
ok = scanAttr(tag);
ch = getChar();
}
if (ch == '?')
{
tag.setClose();
ch = overreadSpaces();
}
if (ch != '>')
{
throw new IOException("ScanTag: Missing '>' (2).");
}
}
else
{
tag = new XMLTag();
if (ch == '/')
{
tag.setClose();
// tag.setName(scanName());
tag.setNameNS(scanNameNS());
ch = overreadSpaces();
if (ch != '>')
{
throw new IOException("ScanTag: Missing '>' (1).");
}
}
else
{
tag.setOpen();
ungetChar(ch);
// tag.setName(scanName());
tag.setNameNS(scanNameNS());
ch = overreadSpaces();
boolean ok = true;
while ((ch != 0x00) && (ch != '/') && (ch != '>') && ok)
{
ungetChar(ch);
ok = scanAttr(tag);
ch = getChar();
}
if (ch == '/')
{
tag.setClose();
ch = overreadSpaces();
}
if (ch != '>')
{
throw new IOException("ScanTag: Missing '>' (2).");
}
}
}
return tag;
}
@Override
public Object getNewToken()
{
char ch;
boolean ex = false;
Object token = null;
try
{
while (!ex)
{
ch = overreadSpaces();
switch (ch)
{
case 0x00:
{
ex = true;
break;
}
case '<':
{
token = scanTag();
ex = (token != null);
break;
}
default:
{
ungetChar(ch);
token = scanContent();
ex = true;
}
}
}
}
catch (IOException e)
{
}
return token;
}
/**
* Start-Tag eines Blatts lesen.
* @param pName der geforderte Name des Blatts.
* @return gelesenes Blatt-Tag als XMLTag
*/
public XMLTag getLeafStart(String pName)
{
Object token;
XMLTag tag = null;
token = getToken();
if (token != null)
{
if (token instanceof XMLTag)
{
tag = (XMLTag) token;
if (tag.isOpen() && ((pName == null) || (tag.getName().equals(pName))))
{
if (tag.isClose())
{
/*
tag.resOpen();
ungetToken(tag);
*/
// Hier m??te eigentlich ein neues schlie?endes XMLTag mit gleichem
// Namen erstellt und dieses zur?ckgelegt werden. Danach sollte
// tag.resClose() aufgerufen werden.
XMLTag t = new XMLTag();
t.setName(tag.getName());
t.setNameSpace(tag.getNameSpace());
t.setClose();
ungetToken(t);
tag.resClose();
}
}
else
{
ungetToken(token);
tag = null;
}
}
else
{
ungetToken(token);
}
}
return tag;
}
/**
* Lesen des Inhalts eines XML-Blatts.
* @return Inhalt als String
* @throws Exception beim Auftreten eines Lesefehlers
*/
public String getLeafContent() throws Exception
{
Object token;
String strg;
token = getToken();
if (token == null)
{
throw new Exception("XMLScanner.getLeafContent: XML-Error.");
}
if (token instanceof String)
{
strg = (String) token;
}
else
{
ungetToken(token);
strg = "";
}
return strg;
}
/**
* End-Tag eines XML-Blatts lesen.
* @param pName der geforderte Name des XML-Blatts
* @return XML-Blatt-Tag als XMLTag
* @throws Exception falls ein Lesefehler auftritt, ebenso im Falle von
* XML-Struktur-Fehlern
*/
public XMLTag getLeafEnd(String pName) throws Exception
{
Object token;
XMLTag tag = null;
token = getToken();
if (token == null)
{
throw new Exception("XMLScanner.getLeafEnd: XML-Error.");
}
if (token instanceof XMLTag)
{
tag = (XMLTag) token;
if (!tag.isOpen() && tag.isClose())
{
if ((pName != null) && (!tag.getName().equals(pName)))
{
ungetToken(token);
tag = null;
}
}
else
{
ungetToken(token);
tag = null;
// throw new Exception("XMLScanner.getLeafEnd: XML-Error.");
}
}
else
{
ungetToken(token);
}
return tag;
}
/**
* Liest ein Blatt eines XML-Baums. Im wesentlichen wird ein Open-Tag mit dem
* angegebenen Namen erwartet, danach ein Wert (Inhalt, Content) und
* schlie?lich ein Close-Tag mit dem gleichen Namen wie beim Open-Tag.
* Ergebnis ist der gelesene Wert. Dieser kann = "" sein. Leer-Tags sind
* erlaubt.
* @param pName - der Name des betreffenden Tags
* @return Wert (Inhalt, Content) des Tags, im Fehlerfall (falsches Open-Tag)
* wird null geliefert.
* @throws Exception falls ein Lesefehler auftritt oder das Close-Tag
* fehlerhaft ist.
*/
public String getLeaf(String pName) throws Exception
{
XMLTag tag;
String strg;
tag = getLeafStart(pName);
if (tag == null)
{
return null;
}
strg = getLeafContent();
if (strg == null)
{
throw new Exception("Error (XMLScanner.getLeaf): Value or " + "" + pName + "> expected.");
}
tag = getLeafEnd(pName);
if (tag == null)
{
throw new Exception("Error (XMLScanner.getLeaf): " + "" + pName + "> expected.");
}
return strg;
}
/**
* String-XML-Blatt lesen.
* @param pName der Name des XML-Blatt-Tags
* @return Inhalt als String
* @throws Exception siehe getLeaf
*/
public String getLeafString(String pName) throws Exception
{
return getLeaf(pName);
}
/**
* Integer-XML-Blatt lesen.
* @param pName der Name des XML-Blatt-Tags
* @return Inhalt als Integer
* @throws Exception siehe getLeaf
*/
public Integer getLeafInt(String pName) throws Exception
{
String s = getLeaf(pName);
return UniTypeConv.convString2Int(s);
}
/**
* Long-XML-Blatt lesen.
* @param pName der Name des XML-Blatt-Tags
* @return Inhalt als Long
* @throws Exception siehe getLeaf
*/
public Long getLeafLong(String pName) throws Exception
{
String s = getLeaf(pName);
return UniTypeConv.convString2Long(s);
}
/**
* Float-XML-Blatt lesen.
* @param pName der Name des XML-Blatt-Tags
* @return Inhalt als Float
* @throws Exception siehe getLeaf
*/
public Float getLeafFloat(String pName) throws Exception
{
String s = getLeaf(pName);
return UniTypeConv.convString2Float(s);
}
/**
* Double-XML-Blatt lesen.
* @param pName der Name des XML-Blatt-Tags
* @return Inhalt als Double
* @throws Exception siehe getLeaf
*/
public Double getLeafDouble(String pName) throws Exception
{
String s = getLeaf(pName);
return UniTypeConv.convString2Double(s);
}
/**
* Date-XML-Blatt lesen.
* @param pName der Name des XML-Blatt-Tags
* @param pMask optionale Maske
* @return Inhalt als Date (nur Datum)
* @throws Exception siehe getLeaf
*/
public Date getLeafDate(String pName, String pMask) throws Exception
{
String s = getLeaf(pName);
return UniTypeConv.convString2Date(s, pMask);
}
/**
* Date-XML-Blatt lesen.
* @param pName der Name des XML-Blatt-Tags
* @return Inhalt als Date (nur Datum)
* @throws Exception siehe getLeaf
*/
public Date getLeafDate(String pName) throws Exception
{
return getLeafDate(pName, null);
}
/**
* Time-XML-Blatt lesen.
* @param pName der Name des XML-Blatt-Tags
* @param pMask optionale Maske
* @return Inhalt als Date (nur Zeitangabe)
* @throws Exception siehe getLeaf
*/
public Date getLeafTime(String pName, String pMask) throws Exception
{
String s = getLeaf(pName);
return UniTypeConv.convString2Time(s, pMask);
}
/**
* Time-XML-Blatt lesen.
* @param pName der Name des XML-Blatt-Tags
* @return Inhalt als Date (nur Zeitangabe)
* @throws Exception siehe getLeaf
*/
public Date getLeafTime(String pName) throws Exception
{
return getLeafTime(pName, null);
}
/**
* DateTime-XML-Blatt lesen.
* @param pName der Name des XML-Blatt-Tags
* @param pMask optionale Maske
* @return Inhalt als Date (Datum und Zeitangabe)
* @throws Exception siehe getLeaf
*/
public Date getLeafDateTime(String pName, String pMask) throws Exception
{
String s = getLeaf(pName);
return UniTypeConv.convString2DateTime(s, pMask);
}
/**
* DateTime-XML-Blatt lesen.
* @param pName der Name des XML-Blatt-Tags
* @return Inhalt als Date (Datum und Zeitangabe)
* @throws Exception siehe getLeaf
*/
public Date getLeafDateTime(String pName) throws Exception
{
return getLeafDateTime(pName, null);
}
/**
* Boolean-XML-Blatt lesen.
* @param pName der Name des XML-Blatt-Tags
* @return Inhalt als Boolean
* @throws Exception siehe getLeaf
*/
public Boolean getLeafBool(String pName) throws Exception
{
String s = getLeaf(pName);
return UniTypeConv.convString2Bool(s);
}
/**
* myLevel erh?hen.
*/
public void incLevel()
{
myLevel++;
}
/**
* myLevel verringern.
*/
public void decLevel()
{
myLevel--;
}
/**
* Ermittlung der LeafHashtable, um z. B. zu pr?fen, ob diese nach Laden
* eines TO's auch wirklich leer ist.
* @return die LHT
*/
public LeafHashtable getLHT()
{
return myLHT;
}
/**
* Spezielle Hashtabelle aus XML-Bl?ttern erzeugen.
* In der Hashtabelle liegen unter den XML-Blatt(-Tag)-Namen (String) als
* Schl?ssel der Wert des jeweiligen Blatts (ebenfalls als String).
* @return diese Hashtabelle
* @throws Exception falls ein Lesefehler auftritt, ebenso im Falle von
* XML-Struktur-Fehlern
*/
public LeafHashtable getLeafs() throws Exception
{
if (myLevel != 0)
{
return myLHT;
}
myLHT = new LeafHashtable();
do
{
Object token = getToken();
if (!(token instanceof XMLTag))
{
ungetToken(token);
return myLHT;
}
XMLTag tag = (XMLTag) token;
if (!tag.isOpen())
{
ungetToken(tag);
return myLHT;
}
String name = tag.getName();
StringBuilder sb = new StringBuilder();
if (!tag.isClose())
{
token = getToken();
if (token instanceof String)
{
sb.append((String) token);
}
else
{
ungetToken(token);
}
token = getToken();
if (!(token instanceof XMLTag))
{
throw new Exception("XML-Error 1 (leaf opening tag: " + name + " / missing leaf closing tag, found: " + (token != null ? token.toString() : "null") + ")");
}
tag = (XMLTag) token;
if (tag.isOpen())
{
throw new Exception("XML-Error 2 (leaf opening tag: " + name + " / missing leaf closing tag, found new opening tag: " + tag.getName() + ")");
}
if (!tag.isClose())
{
throw new Exception("XML-Error 3 (leaf opening tag: " + name + " / found invalid tag: " + tag.getName() + ")");
}
if (!tag.getName().equals(name))
{
throw new Exception("XML-Error 4 (leaf opening tag: " + name + " / found invalid leaf closing tag: " + tag.getName() + ")");
}
}
myLHT.put(name, sb.toString());
}
while (true);
}
/**
* Pr?fen, ob die XML-Leaf-Hash-Tabelle (LHT) leer ist,
* falls nicht: Meldungen ausgeben.
* @param lht die LHT
* @param text Text f?r die Meldung.
*/
public static void checkLHT(LeafHashtable lht, String text)
{
if ((lht != null) && !lht.isEmpty())
{
lht.forEach((key, value) -> MLogger.wrn("LHT (" + text + ") not empty: " + "<" + key + ">" + value + "" + key + ">"));
/*
Enumeration e = lht.keys();
while (e.hasMoreElements())
{
String key = (String) e.nextElement();
String val = (String) lht.get(key);
MLogger.wrn("LHT (" + text + ") not empty: " + "<" + key + ">" + val + "" + key + ">");
}
*/
MLogger.wrn("Please check XML-Source-File with DTD or XSD!");
}
}
/**
* Pr?fen, ob die XML-Leaf-Hash-Tabelle (LHT) leer ist,
* falls nicht: Meldungen ausgeben,
* Darf nur nach getLeafs() verwendet werden, bezieht sich auf das letzte
* getLeafs().
* @param text Text f?r die Meldung.
*/
public void checkLHT(String text)
{
checkLHT(myLHT, text);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy