今天天分享下如何通过组合模式,在sd卡中,写一个xml文件来保存这个组合类,及如何读取,解析自定义的xml文件,没有使用w3c 组织所推荐的任何一种接口,自己实现对自定义xml文件的解析,因为能力不够,所以实现下基本原理,加深对xml文件解析原理的理解。
上一篇博客我发现都没写出来我对组合模式的理解,很尴尬,文笔不好呢。
先简略说一下组合模式,这个组合模式,是我对复杂,嵌套xml文件的解析、生成的基础。
组合模式,其实是对现实生活中树的抽象,一颗树,他有树干,树枝,树叶,树干中又有树枝、树叶,树叶中什么都没有,只是最小的单位,而树干可以说只是大号的树枝,所有,一颗树就两种,树枝和树叶。抽象为一个类的话就是,一个数枝类中,有一个list包含本树枝中所有包含的所有树枝树干,也就是子节点。当画这颗树的时候,我们其实,是去调用总的父节点,让父节点去循环调用他的子节点,有的子节点又是第二级父节点,他又会去调用自己的子节点,第三级、第四级等等,一直到把所有的节点都递归调用出来。如果还不是不清楚,请看我上一篇博客https://my.oschina.net/zhenghaoLi/blog/1519550
一个xml文件不仅仅只有一层,有根,有叶,有属性等等,我们暂时用不了这么多,我使用xml文件,只是为了,方便查看层级结构,即使不用xml文件,json、Excel、txt等也都是可以的。先看我自定义的xml文件层级结构。
1<?xml version="1.0" encoding="utf-8" ?> 2 <Composite> 3 <Composite> 4 <Ellipse> 5 <rectFTop>40.0</rectFTop> 6 <rectFLeft>50.0</rectFLeft> 7 <rectFRight>700.0</rectFRight> 8 <rectFBottom>200.0</rectFBottom> 9 <paintStyle>STROKE</paintStyle> 10 <paintWight>20.0</paintWight> 11 <paintAlpha>100</paintAlpha> 12 </Ellipse> 13 <Path> 14 <pathX>100.0</pathX> 15 <pathY>100.0</pathY> 16 <pathX>100.0</pathX> 17 <pathY>300.0</pathY> 18 <pathX>200.0</pathX> 19 <pathY>500.0</pathY> 20 <pathX>310.0</pathX> 21 <pathY>265.0</pathY> 22 <pathX>223.0</pathX> 23 <pathY>316.0</pathY> 24 <pathX>265.0</pathX> 25 <pathY>265.0</pathY> 26 <pathX>20.0</pathX> 27 <pathY>100.0</pathY> 28 <paintStyle>STROKE</paintStyle> 29 <paintWight>20.0</paintWight> 30 <paintAlpha>100</paintAlpha> 31 </Path> 32 </Composite> 33 <Composite> 34 <Rectangle> 35 <rectFTop>300.0</rectFTop> 36 <rectFLeft>100.0</rectFLeft> 37 <rectFRight>400.0</rectFRight> 38 <rectFBottom>500.0</rectFBottom> 39 <paintStyle>STROKE</paintStyle> 40 <paintWight>20.0</paintWight> 41 <paintAlpha>100</paintAlpha> 42 </Rectangle> 43 <Circular> 44 <circularX>100.0</circularX> 45 <circularY>100.0</circularY> 46 <circularR>100.0</circularR> 47 <paintStyle>STROKE</paintStyle> 48 <paintWight>20.0</paintWight> 49 <paintAlpha>100</paintAlpha> 50 </Circular> 51 <Line> 52 <lineX>200.0</lineX> 53 <lineY>100.0</lineY> 54 <lineX>300.0</lineX> 55 <lineY>200.0</lineY> 56 <lineX>100.0</lineX> 57 <lineY>400.0</lineY> 58 <lineX>410.0</lineX> 59 <lineY>165.0</lineY> 60 <lineX>423.0</lineX> 61 <lineY>216.0</lineY> 62 <lineX>665.0</lineX> 63 <lineY>465.0</lineY> 64 <lineX>20.0</lineX> 65 <lineY>100.0</lineY> 66 <paintStyle>STROKE</paintStyle> 67 <paintWight>20.0</paintWight> 68 <paintAlpha>100</paintAlpha> 69 </Line> 70 </Composite> 71</Composite>
这个xml文件中包含画出一个图形所有需要的信息,我们先看看生成这个文件的,复杂类的结构
dataRoot.addChild(dataroot1); dataRoot.addChild(dataroot2); dataroot1.addChild(dataLeaf1); dataroot1.addChild(dataLeaf2); dataroot2.addChild(dataLeaf3); dataroot2.addChild(dataLeaf4); dataroot2.addChild(dataLeaf5);
写文件,写文件,我是用java io去写的,一行一行的追加进去,所以,我们需要在定义一个接口,让数据类去实现这个接口,我们就能在外部进行调用,而不用管是谁执行了这个接口,
public interface DataInterface { public void addChild(BaseData baseData); public void removeChild(int index); public void move(float moveX ,float moveY); public void draw(Canvas canvas); public List<BaseData> getChild(); public DataType[] pointIsInside(float moveX , float moveY); public void writer(BufferedWriter bufferedWriter); public BaseData read(BufferedReader bufferedReader); }
在这个接口类中,我把io流的传进去,io流是这样的,这就像一个管道,你开了口子,你不关闭,这个口子就一直存在,可以随意的对文件的内容进行操作,在Android中,除了基本数据类型传递的时候是copy,其他的任何数据类型都是指针的引用,所以不用考虑,在子类中我当前所操作的这个对象是不是我想要那个对象。
写xml:
数据的父类中的wrier方法
@Override public void writer(BufferedWriter bufferedWriter) {
}
在叶子中把自己的属性写进去,重写writer方法
@Override public void writer(BufferedWriter bufferedWriter) { float x = getFloats()[0]; float y = getFloats()[1]; float r = getFloats()[2]; StringBuilder s = new StringBuilder(); s.append("<Circular>\r\n"); s.append("<circularX>" + x + "</circularX>\r\n"); s.append("<circularY>" + y + "</circularY>\r\n"); s.append("<circularR>" + r + "</circularR>\r\n"); s.append("<paintStyle>" + getPaintStyle() + "</paintStyle>\r\n"); s.append("<paintWight>" + getWight() + "</paintWight>\r\n"); s.append("<paintAlpha>" + getPaintAlpha() + "</paintAlpha>\r\n"); s.append("</Circular>\r\n"); try { bufferedWriter.write(s.toString()); } catch (IOException e) { e.printStackTrace(); } }
在树枝中重写writer方法,遍历自己的子类的writer方法
@Override public void writer(BufferedWriter bufferedWriter) { String s = "<Composite>\r\n"; String s1 = "</Composite>\r\n"; try { bufferedWriter.write(s); if (childComponents != null) { for (BaseData baseData : childComponents) { baseData.writer(bufferedWriter); } } bufferedWriter.write(s1); } catch (IOException e) { e.printStackTrace(); } }
我们一行一行的把字符串写进文件,为什么后面是\r\n,这是因为,我解析xml文件时需要一行一行的读取字符串,好判断我需要生成的是树枝还是树叶,\r\n因为在Windows与Linux对换行的编码不同,Linux为\n,Windows为\r\n ,为了有换行的效果,最好使用\r\n。childComponents 是一个list ,其中包含了这个树枝中包含的所有子类,他是如何加进去的,参考我的上一篇博客。
main
public class MainActivity extends AppCompatActivity { RelativeLayout relativeLayout; DrawView drawView; BaseData dataRoot; String FILE_PATH = "/sdcard/ClockIn/"; String FILE_NAME = "1234567.xml";
1List<BaseData> dataList; 2String szie = null; 3float x = 0; 4float y = 0; 5List<Float> floatList = new ArrayList<>(); 6 7@Override 8protected void onCreate(Bundle savedInstanceState) { 9 super.onCreate(savedInstanceState); 10 setContentView(R.layout.activity\_main); 11 relativeLayout = (RelativeLayout) findViewById(R.id.main\_relativeLayout); 12 drawView = new DrawView(this); 13 relativeLayout.addView(drawView); 14 15 dataRoot = new CompositeData(); 16 BaseData dataroot1 = new CompositeData(); 17 BaseData dataroot2 = new CompositeData(); 18 19 RectF rectF = new RectF(); 20 rectF.top = 40; 21 rectF.right = 700; 22 rectF.left = 50; 23 rectF.bottom = 200;
// float left, float top, float right, float bottom BaseData dataLeaf1 = new EllipseData(); dataLeaf1.setRectF(rectF); dataLeaf1.setPaintStyle(Paint.Style.STROKE); dataLeaf1.setWight(20); dataLeaf1.setPaintAlpha(100);
1 float\[\] leaf2Float = {100, 100, 100, 300, 200, 500, 310, 265, 223, 316, 265, 265}; 2 BaseData dataLeaf2 = new PathData(); 3 dataLeaf2.setPaintStyle(Paint.Style.STROKE); 4 dataLeaf2.setWight(20); 5 dataLeaf2.setPaintAlpha(100); 6 dataLeaf2.setFloats(leaf2Float); 7 8 9 RectF rectF1 = new RectF(100, 300, 400, 500); 10 BaseData dataLeaf3 = new RectangleData(); 11 dataLeaf3.setRectF(rectF1); 12 dataLeaf3.setPaintStyle(Paint.Style.STROKE); 13 dataLeaf3.setWight(20); 14 dataLeaf3.setPaintAlpha(100); 15 16 17 float\[\] leaf4Float = {100, 100, 100, 100, 300, 600, 410, 165, 423, 216, 665, 465}; 18 BaseData dataLeaf4 = new CircularData(); 19 dataLeaf4.setFloats(leaf4Float); 20 dataLeaf4.setPaintStyle(Paint.Style.STROKE); 21 dataLeaf4.setWight(20); 22 dataLeaf4.setPaintAlpha(100); 23 24 25 float\[\] leaf5Float = {200, 100, 300, 200, 100, 400, 410, 165, 423, 216, 665, 465}; 26 BaseData dataLeaf5 = new LineData(); 27 dataLeaf5.setFloats(leaf5Float); 28 dataLeaf5.setPaintStyle(Paint.Style.STROKE); 29 dataLeaf5.setWight(20); 30 dataLeaf5.setPaintAlpha(100); 31 32 33 dataRoot.addChild(dataroot1); 34 dataRoot.addChild(dataroot2); 35 dataroot1.addChild(dataLeaf1); 36 dataroot1.addChild(dataLeaf2); 37 dataroot2.addChild(dataLeaf3); 38 dataroot2.addChild(dataLeaf4); 39 dataroot2.addChild(dataLeaf5); 40 41 drawView.upData(dataRoot);
// generatedFile(dataRoot, FILE_PATH, FILE_NAME); // relativeLayout.setOnTouchListener(new TouchListenerImp()); readFile(FILE_PATH, FILE_NAME); }
/** * 数据写入文件 */ public void generatedFile(BaseData baseData, String filePath, String fileName) { BufferedOutputStream bufferedOutputStream = null; OutputStreamWriter outputStreamWriter = null; BufferedWriter bufferedWriter = null; try { File file = new File(String.valueOf(makeFilePath(filePath, fileName))); // 如果文件存在就删除它 if (file.exists()) { file.delete(); } else { file.mkdir(); }
1 OutputStream outputStream = new FileOutputStream(file); 2 bufferedOutputStream = new BufferedOutputStream(outputStream); 3 outputStreamWriter = new OutputStreamWriter(bufferedOutputStream); 4 bufferedWriter = new BufferedWriter(outputStreamWriter); 5 bufferedWriter.write("<?xml version=\\"1.0\\" encoding=\\"utf-8\\"?>\\r\\n"); 6 baseData.writer(bufferedWriter); 7 bufferedWriter.flush(); 8} catch (FileNotFoundException e) { 9 e.printStackTrace(); 10} catch (IOException e) { 11 e.printStackTrace(); 12} finally { 13 try { 14 bufferedWriter.close(); 15 } catch (IOException e) { 16 e.printStackTrace(); 17 } 18}
}
// 生成文件夹 public static void makeRootDirectory(String filePath) { File file = null; try { file = new File(filePath); if (!file.exists()) { file.mkdir(); } } catch (Exception e) { Log.i("error:", e + ""); } }
// 生成文件 public File makeFilePath(String filePath, String fileName) { File file = null; makeRootDirectory(filePath); try { file = new File(filePath + fileName);
1 if (!file.exists()) { 2 file.createNewFile(); 3 } 4} catch (Exception e) { 5 e.printStackTrace(); 6} 7return file;
}
在main中,写文件时,我是把bufferedWriter传给了父节点,让父节点写出所有的东西,文件写好后,记得关闭流。
读:
xml的解析,有几种方式,总的来说就两种,一是流式解析,一是整体加载,整体加载我不是很明白,先来流式解析吧,xml解析,是在xml文档中,一行一行的解析字符串,比如,遇到代表树叶的“<树叶>”,马上通过工厂方法new 一个树叶对象出来,然后,在通过字符串,解析其中的属性,比如“<长>”,然后,把“<长>123</长>”中的123取出来,赋值给这个对象的 长属性,遇到“</树叶>”是,停止对bufferedWriter 的读取,因为只要一个地方读取了一行,bufferedWriter内的指针就会指向下一个,
树叶对文件的读取及返回自身
@Override public BaseData read(BufferedReader bufferedReader) { String tempString = ""; List<Float> floatList = new ArrayList<>(); try { while ((tempString = bufferedReader.readLine()) != null) { if (tempString.contains("</Circular>")) { break; } if (tempString.contains("<circularX>") || tempString.contains("<circularY>") || tempString.contains("<circularR>")) { String s = GetNumbers(tempString); if (s.length() != 0) { floatList.add(Float.parseFloat(s)); } } CommonAttributeAssignment(tempString); } } catch (IOException e) { e.printStackTrace(); }
1float\[\] floats = new float\[floatList.size()\]; 2for (int i = 0; i < floatList.size(); i++) { 3 floats\[i\] = floatList.get(i); 4} 5setFloats(floats); 6return this;
}
在自定义的xml文件中,有一些是所有树叶所共有的,所以抽出来,写在父类中
//公共属性赋值 void CommonAttributeAssignment(String tempString) { String s = GetNumbers(tempString); if (s.length() != 0) { if (tempString.contains("<rectFTop>")) { getRectF().top = Float.parseFloat(s); } if (tempString.contains("<rectFLeft>")) { getRectF().left = Float.parseFloat(s); } if (tempString.contains("<rectFRight>")) { getRectF().right = Float.parseFloat(s); } if (tempString.contains("<rectFBottom>")) { getRectF().bottom = Float.parseFloat(s); } if (tempString.contains("<paintWight>")) { setWight(Float.parseFloat(GetNumbers(tempString))); } if (tempString.contains("<paintAlpha>")) { setPaintAlpha(Integer.parseInt(GetNumbers(tempString))); } }
1if (tempString.contains("<paintStyle>")) { 2 if (tempString.contains("FILL")) { 3 setPaintStyle(Paint.Style.FILL); 4 } 5 if (tempString.contains("FILL\_AND\_STROKE")) { 6 setPaintStyle(Paint.Style.FILL\_AND\_STROKE); 7 } 8 if (tempString.contains("STROKE")) { 9 setPaintStyle(Paint.Style.STROKE); 10 } 11}
}
取出xml文件中,取出的一行字符串取出其中数字
//截取数字 String GetNumbers(String tempString) {
1Pattern p = Pattern.compile("\[0-9\\\\.\]+"); 2Matcher m = p.matcher(tempString.trim()); 3while (m.find()) { 4 return m.group(); 5} 6return "";
}
也写在父类里边,为什么不写静态方法,是因为,我在那个类里进行的赋值,只会赋给当前类
树枝对文件的读取及返回自身
@Override public BaseData read(BufferedReader bufferedReader) { String tempString = ""; try { while ((tempString = bufferedReader.readLine()) != null) { if (tempString.contains("</Composite>")) { break; } addChild(StyleView.getBaseData(tempString).read(bufferedReader)); } } catch (IOException e) { e.printStackTrace(); } return this; }
StyleView.getBaseData(tempString).read(bufferedReader) 是使用解析出来的的字符串,通过getBaseData工厂方法来生成对应对象的方法,然后调用生成对象的read方法,返回这个属性已经被赋值了的树叶、树枝类,然后添加到树枝的list中去
StyleView.getBaseData(tempString) 生成xml对应类的工厂方法
public static BaseData getBaseData(String typeEnum) { BaseData baseData = new BaseData(); if (typeEnum.contains(TypeEnum.Circular.toString())){ return new CircularData(); } if (typeEnum.contains(TypeEnum.Ellipse.toString())){ return new EllipseData(); } if (typeEnum.contains(TypeEnum.Line.toString())){ return new LineData(); } if (typeEnum.contains(TypeEnum.Path.toString())){ return new PathData(); } if (typeEnum.contains(TypeEnum.Rectangle.toString())){ return new RectangleData(); }
1if (typeEnum.contains(TypeEnum.Composite.toString())){ 2 return new CompositeData(); 3} 4 5return baseData;
}
TypeEnum 对类的判断
public enum TypeEnum { Circular, Ellipse, Line, Path, Rectangle, Composite; }
main的read方法,xml格式化,以行为单位解析xml,是解析xml的核心,通过一行一行的解析,我们可以根据这行字符串中内容进行:对应类的生成、进行类的属性赋值。
/** * 以行为单位读取文件,常用于读面向行的格式化文件 */ public void readFile(String filePath, String fileName) { File file = new File(filePath + fileName); BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(file)); String tempString = null;
1 BaseData baseData = new BaseData(); 2 while ((tempString = reader.readLine()) != null) { 3 if (tempString.length() == 0 || tempString.contains("<?xml version=\\"1.0\\" encoding=\\"utf-8\\"?>")) { 4 continue; 5 } 6 baseData = StyleView.getBaseData(tempString).read(reader); 7 } 8 reader.close(); 9 drawView.upData(baseData); 10 generatedFile(baseData, filePath, fileName); 11} catch (IOException e) { 12 e.printStackTrace(); 13} finally { 14 if (reader != null) { 15 try { 16 reader.close(); 17 } catch (IOException e1) { 18 e1.printStackTrace(); 19 } 20 } 21}
}
过滤掉头文件,与所有的空行,然后画出这个复杂类,在生成一下这个类的xml文件,对比两个xml是否相同,反正我对比的时候是相同的xml结构与属性。自定义xml的生成与解析到这儿就完了。有点长。
如果不是格式化的xml文件怎么办,其实,所谓的格式化xml,所谓的换行,只是在你想要换行的后面加一个 \r\n 或 \n 而已,BufferedReader 其实是java io一个字节一个字节的读取,然后放到字节buffer中,当往字节buffer中加数据是,判断读取到\n 或 \n 时,暂停读取,字节buffer转换为BufferedReader。BufferedReader.readLine(),就是继续开始读取。